From c1c4dd59237aac677c32b8f4075eb0eb6934d009 Mon Sep 17 00:00:00 2001 From: kebernet Date: Fri, 1 Apr 2011 04:48:20 +0000 Subject: [PATCH] Initial checkin of Propono --- CHANGES | 58 ++ LICENSE | 203 +++++ NOTICE | 26 + pom.xml | 292 ++++++ .../atom/client/AtomClientFactory.java | 47 + .../propono/atom/client/AuthStrategy.java | 30 + .../atom/client/BasicAuthStrategy.java | 41 + .../atom/client/ClientAtomService.java | 138 +++ .../propono/atom/client/ClientCategories.java | 70 ++ .../propono/atom/client/ClientCollection.java | 217 +++++ .../propono/atom/client/ClientEntry.java | 259 ++++++ .../propono/atom/client/ClientMediaEntry.java | 322 +++++++ .../propono/atom/client/ClientWorkspace.java | 66 ++ .../propono/atom/client/EntryIterator.java | 124 +++ .../atom/client/GDataAuthStrategy.java | 67 ++ .../propono/atom/client/NoAuthStrategy.java | 31 + .../propono/atom/client/OAuthStrategy.java | 302 +++++++ .../atom/client/atomclient-diagram.gif | Bin 0 -> 19120 bytes .../propono/atom/common/AtomService.java | 122 +++ .../propono/atom/common/Categories.java | 155 ++++ .../propono/atom/common/Collection.java | 252 ++++++ .../propono/atom/common/Workspace.java | 147 +++ .../propono/atom/common/rome/AppModule.java | 42 + .../atom/common/rome/AppModuleGenerator.java | 85 ++ .../atom/common/rome/AppModuleImpl.java | 69 ++ .../atom/common/rome/AppModuleParser.java | 66 ++ .../propono/atom/server/AtomException.java | 53 ++ .../propono/atom/server/AtomHandler.java | 144 +++ .../atom/server/AtomHandlerFactory.java | 113 +++ .../atom/server/AtomMediaResource.java | 95 ++ .../server/AtomNotAuthorizedException.java | 49 + .../atom/server/AtomNotFoundException.java | 48 + .../propono/atom/server/AtomRequest.java | 139 +++ .../propono/atom/server/AtomRequestImpl.java | 114 +++ .../propono/atom/server/AtomServlet.java | 378 ++++++++ .../server/FactoryConfigurationError.java | 106 +++ .../propono/atom/server/FactoryFinder.java | 280 ++++++ .../propono/atom/server/SecuritySupport.java | 90 ++ .../server/impl/FileBasedAtomHandler.java | 443 +++++++++ .../impl/FileBasedAtomHandlerFactory.java | 37 + .../server/impl/FileBasedAtomService.java | 188 ++++ .../atom/server/impl/FileBasedCollection.java | 837 ++++++++++++++++++ .../atom/server/impl/FileBasedWorkspace.java | 34 + .../propono/atom/server/impl/FileStore.java | 116 +++ .../propono/blogclient/BaseBlogEntry.java | 193 ++++ .../syndication/propono/blogclient/Blog.java | 213 +++++ .../blogclient/BlogClientException.java | 40 + .../propono/blogclient/BlogConnection.java | 34 + .../blogclient/BlogConnectionFactory.java | 80 ++ .../propono/blogclient/BlogEntry.java | 231 +++++ .../propono/blogclient/BlogResource.java | 39 + .../blogclient/atomprotocol/AtomBlog.java | 206 +++++ .../atomprotocol/AtomCollection.java | 165 ++++ .../atomprotocol/AtomConnection.java | 92 ++ .../blogclient/atomprotocol/AtomEntry.java | 240 +++++ .../atomprotocol/AtomEntryIterator.java | 72 ++ .../blogclient/atomprotocol/AtomResource.java | 134 +++ .../propono/blogclient/blogclient-diagram.gif | Bin 0 -> 18568 bytes .../blogclient/metaweblog/MetaWeblogBlog.java | 436 +++++++++ .../metaweblog/MetaWeblogConnection.java | 105 +++ .../metaweblog/MetaWeblogEntry.java | 122 +++ .../metaweblog/MetaWeblogResource.java | 112 +++ .../propono/utils/ProponoException.java | 153 ++++ .../syndication/propono/utils/Utilities.java | 268 ++++++ src/main/java/propono-version.properties | 1 + src/main/java/rome.properties | 7 + .../propono/atom/client/AtomClientTest.java | 452 ++++++++++ .../atom/client/BloggerDotComTest.java | 86 ++ .../propono/atom/common/AtomServiceTest.java | 156 ++++ .../propono/atom/common/CollectionTest.java | 67 ++ .../atom/server/AtomClientServerTest.java | 122 +++ .../atom/server/TestAtomHandlerFactory.java | 27 + .../atom/server/TestAtomHandlerImpl.java | 31 + .../blogclient/SimpleBlogClientTest.java | 215 +++++ src/test/resources/commons-logging.properties | 16 + src/test/resources/duke-wave-shadow.gif | Bin 0 -> 2700 bytes src/test/resources/servicedoc1.xml | 18 + src/test/resources/simplelog.properties | 19 + 78 files changed, 10647 insertions(+) create mode 100644 CHANGES create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 pom.xml create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/AtomClientFactory.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/AuthStrategy.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/BasicAuthStrategy.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/ClientAtomService.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/ClientCategories.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/ClientCollection.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/ClientEntry.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/ClientMediaEntry.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/ClientWorkspace.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/EntryIterator.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/GDataAuthStrategy.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/NoAuthStrategy.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/OAuthStrategy.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/client/atomclient-diagram.gif create mode 100644 src/main/java/com/sun/syndication/propono/atom/common/AtomService.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/common/Categories.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/common/Collection.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/common/Workspace.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/common/rome/AppModule.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleGenerator.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleImpl.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleParser.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/AtomException.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/AtomHandler.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/AtomHandlerFactory.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/AtomMediaResource.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/AtomNotAuthorizedException.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/AtomNotFoundException.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/AtomRequest.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/AtomRequestImpl.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/AtomServlet.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/FactoryConfigurationError.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/FactoryFinder.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/SecuritySupport.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomHandler.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomHandlerFactory.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomService.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedCollection.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedWorkspace.java create mode 100644 src/main/java/com/sun/syndication/propono/atom/server/impl/FileStore.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/BaseBlogEntry.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/Blog.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/BlogClientException.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/BlogConnection.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/BlogConnectionFactory.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/BlogEntry.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/BlogResource.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomBlog.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomCollection.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomConnection.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomEntry.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomEntryIterator.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomResource.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/blogclient-diagram.gif create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/metaweblog/MetaWeblogBlog.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/metaweblog/MetaWeblogConnection.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/metaweblog/MetaWeblogEntry.java create mode 100644 src/main/java/com/sun/syndication/propono/blogclient/metaweblog/MetaWeblogResource.java create mode 100644 src/main/java/com/sun/syndication/propono/utils/ProponoException.java create mode 100644 src/main/java/com/sun/syndication/propono/utils/Utilities.java create mode 100644 src/main/java/propono-version.properties create mode 100644 src/main/java/rome.properties create mode 100644 src/test/java/com/sun/syndication/propono/atom/client/AtomClientTest.java create mode 100644 src/test/java/com/sun/syndication/propono/atom/client/BloggerDotComTest.java create mode 100644 src/test/java/com/sun/syndication/propono/atom/common/AtomServiceTest.java create mode 100644 src/test/java/com/sun/syndication/propono/atom/common/CollectionTest.java create mode 100644 src/test/java/com/sun/syndication/propono/atom/server/AtomClientServerTest.java create mode 100644 src/test/java/com/sun/syndication/propono/atom/server/TestAtomHandlerFactory.java create mode 100644 src/test/java/com/sun/syndication/propono/atom/server/TestAtomHandlerImpl.java create mode 100644 src/test/java/com/sun/syndication/propono/blogclient/SimpleBlogClientTest.java create mode 100644 src/test/resources/commons-logging.properties create mode 100644 src/test/resources/duke-wave-shadow.gif create mode 100644 src/test/resources/servicedoc1.xml create mode 100644 src/test/resources/simplelog.properties diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..0b2c899 --- /dev/null +++ b/CHANGES @@ -0,0 +1,58 @@ + +ROME Propono Subproject + + +ROME Propono 0.6, September 30, 2007 +------------------------------------ + +* Updated for APP final (draft #17) w/new APP URI "http://www.w3.org/2007/app" + +* Tested file-based server against Tim Bray's Ape (from CVS September 30, 2007) + with some warning and one error that is apparently due to a bug in Ape. + +* Now includes pre-release of ROME 0.9.1 with key Atom parse fixes. + +* Changed arguements in Atom server's AtomHandler interface to accept + AtomRequest objects instead of String[] pathinfo arrays. + +* Added support for relative URIs in the Service Document + - Fixes https://rome.dev.java.net/issues/show_bug.cgi?id=67 + - Added Collection.getHrefResolved() + - Added Categories.getHrefResolved() + +* Added new options to the file-based server's propono.properties file so you + can turn on/off relative URIs and inline categories. + propono.atomserver.filebased.relativeURIs=true + propono.atomserver.filebased.inlineCategories=true + +* Added support for out-of-line categories in Atom client classes + - Added new Categories.href property + - New ClientCategories classes can fetch remote categories from href URI + - Fixes https://rome.dev.java.net/issues/show_bug.cgi?id=68 + +* Added support for out-of-line categories in Atom server classes + - New AtomHandler.getCategoriesDocument(String[] pathInfo) method + - New AtomHandler.isCategoriesDocumentURI(String[] pathInfo) method + +* Renamed Introspection to Service Document + - AtomHandler.isIntrospectionURI() -> AtomHandler.isSerivceDocumentURI() + - AtomHandler.getIntrospection() -> AtomHandler.getServiceDocument() + - Added String[] pathInfo argument to getServiceDocument() + +* Renamed PubControlModule to AppModule becuase it also supports app:edited + - Added rome.properties file to configure AppModule + + +ROME Propono 0.5, April 23, 2007 +-------------------------------- + +* Fixes in Blog Client constructors +* AtomServlet uses application/atomsvc+xml for the Service Document +* Fixed issue #66: don't expect entry to be returned from update +* Made example builds more configurable + + +ROME Propono 0.4, April 10, 2007 +-------------------------------- + +* Initial release diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6b0b127 --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ + + 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. + diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..fe019d6 --- /dev/null +++ b/NOTICE @@ -0,0 +1,26 @@ + +Portions of the Propono code are Copyright (C) 2007 Sun Microsystems, Inc. + +Sun Microsystems, Inc. +4150 Network Circle, Santa Clara, California 95054, U.S.A. +All rights reserved. + +U.S. Government Rights - Commercial software. Government users are subject to +the Sun Microsystems, Inc. standard license agreement and applicable provisions +of the FAR and its supplements. + +Use is subject to license terms. + +This distribution may include materials developed by third parties. + +Sun, Sun Microsystems, the Sun logo and Java are trademarks or registered +trademarks of Sun Microsystems, Inc. in the U.S. and other countries. + +This product is covered and controlled by U.S. Export Control laws and may be +subject to the export or import laws in other countries. Nuclear, missile, +chemical biological weapons or nuclear maritime end uses or end users, whether +direct or indirect, are strictly prohibited. Export or reexport to countries +subject to U.S. embargo or to entities identified on U.S. export exclusion +lists, including, but not limited to, the denied persons and specially +designated nationals lists is strictly prohibited. + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..cd35968 --- /dev/null +++ b/pom.xml @@ -0,0 +1,292 @@ + + + 4.0.0 + org.rometools + rome-propono\ + rome-propono + 1.1-SNAPSHOT + jar + The ROME Propono subproject is a Java class library that + supports publishing protocols, specifically the Atom Publishing Protocol + and the legacy MetaWeblog API. Propono includes an Atom client library, + Atom server framework and a Blog client that supports both Atom protocol + and the MetaWeblog API. + + + http://www.rometools.org + + https://rometools.jira.com/browse/PROPONO + + + + + +
dev@rome.dev.java.net
+
+
+
+
+ 2007 + + + dev@rome.dev.java.net + + https://rome.dev.java.net/servlets/ProjectMailingListList + + + https://rome.dev.java.net/servlets/ProjectMailingListList + + + https://rome.dev.java.net/servlets/SummarizeList?listName=dev + + + + + + Dave Johnson + http://rollerweblogger.org/roller + -5 + + + Robert Cooper + http://www.screaming-penguin.com + -4 + kebernet@gmail.com + + + + ROME Project + http://www.rometools.org + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + scm:svn:https://rometools.jira.com/svn/MODULES/trunk + scm:svn:https://rometools.jira.com/svn/MODULES/trunk + https://rometools.jira.com/source/browse/MODULES + + + + oauth + http://oauth.googlecode.com/svn/code/maven/ + + + + + install + + + org.apache.maven.plugins + maven-release-plugin + 2.0-beta-9 + + forked-path + + + + maven-compiler-plugin + + 1.5 + 1.5 + + + + maven-javadoc-plugin + + com.sun.syndication.propono.atom.server.impl + + + + maven-surefire-plugin + + + **/Test*.java + + + + + + org.apache.maven.plugins + maven-source-plugin + + + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + jar + + + + + + + + + + + + org.rometools + rome + 1.1-SNAPSHOT + + + + commons-codec + commons-codec + 1.3 + + + + commons-httpclient + commons-httpclient + 3.0.1 + + + + commons-lang + commons-lang + 2.4 + + + + commons-logging + commons-logging + 1.0.4 + + + + commons-beanutils + commons-beanutils + 1.6 + + + + ws-commons-util + ws-commons-util + 1.0.1 + + + + xmlrpc + xmlrpc-client + 3.0 + + + + xmlrpc + xmlrpc-common + 3.0 + + + + javax.activation + activation + 1.1 + + + + javax.servlet + servlet-api + 2.5 + + + + net.oauth.core + oauth + 20090531 + compile + + + + net.oauth.core + oauth-provider + 20090531 + compile + + + + junit + junit + 3.8.2 + test + + + + org.mortbay.jetty + jetty + 4.2.12 + test + + + + + + + + maven-changes-plugin + + ${basedir}/xdocs/changes.xml + + + + + + + + sonatype-nexus-staging + Nexus Release Repository + http://oss.sonatype.org/service/local/staging/deploy/maven2 + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + http://oss.sonatype.org/content/repositories/snapshots + + + + + + release-sign-artifacts + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.0-alpha-4 + + + sign-artifacts + verify + + sign + + + + + + + + + +
diff --git a/src/main/java/com/sun/syndication/propono/atom/client/AtomClientFactory.java b/src/main/java/com/sun/syndication/propono/atom/client/AtomClientFactory.java new file mode 100644 index 0000000..4e7935b --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/AtomClientFactory.java @@ -0,0 +1,47 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.propono.utils.ProponoException; +import com.sun.syndication.io.impl.Atom10Parser; + + +/** + * Creates AtomService or ClientCollection based on username, password and + * end-point URI of Atom protocol service. + */ +public class AtomClientFactory { + + static { + Atom10Parser.setResolveURIs(true); + } + + /** + * Create AtomService by reading service doc from Atom Server. + */ + public static ClientAtomService getAtomService( + String uri, AuthStrategy authStrategy) throws ProponoException { + return new ClientAtomService(uri, authStrategy); + } + + /** + * Create ClientCollection bound to URI. + */ + public static ClientCollection getCollection( + String uri, AuthStrategy authStrategy) throws ProponoException { + return new ClientCollection(uri, authStrategy); + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/client/AuthStrategy.java b/src/main/java/com/sun/syndication/propono/atom/client/AuthStrategy.java new file mode 100644 index 0000000..e8bd37f --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/AuthStrategy.java @@ -0,0 +1,30 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.propono.utils.ProponoException; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethodBase; + + +public interface AuthStrategy { + + /** + * Add authentication credenticals, tokens, etc. to HTTP method + */ + void addAuthentication(HttpClient httpClient, HttpMethodBase method) + throws ProponoException; +} diff --git a/src/main/java/com/sun/syndication/propono/atom/client/BasicAuthStrategy.java b/src/main/java/com/sun/syndication/propono/atom/client/BasicAuthStrategy.java new file mode 100644 index 0000000..ce200f5 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/BasicAuthStrategy.java @@ -0,0 +1,41 @@ +/* + * Copyright 2009 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.io.impl.Base64; +import com.sun.syndication.propono.utils.ProponoException; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethodBase; + + +public class BasicAuthStrategy implements AuthStrategy { + private String credentials; + + public BasicAuthStrategy(String username, String password) { + this.credentials = + new String(new Base64().encode((username + ":" + password).getBytes())); + } + + public void init() throws ProponoException { + // op-op + } + + public void addAuthentication(HttpClient httpClient, HttpMethodBase method) throws ProponoException { + httpClient.getParams().setAuthenticationPreemptive(true); + String header = "Basic " + credentials; + method.setRequestHeader("Authorization", header); + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/client/ClientAtomService.java b/src/main/java/com/sun/syndication/propono/atom/client/ClientAtomService.java new file mode 100644 index 0000000..873d824 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/ClientAtomService.java @@ -0,0 +1,138 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.io.impl.Atom10Parser; +import com.sun.syndication.propono.utils.ProponoException; +import com.sun.syndication.propono.atom.common.AtomService; +import java.io.InputStreamReader; +import java.util.Iterator; +import java.util.List; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.input.SAXBuilder; + + +/** + * This class models an Atom Publising Protocol Service Document. + * It extends the common + * {@link com.sun.syndication.propono.atom.common.Collection} + * class to add a getEntry() method and to return + * {@link com.sun.syndication.propono.atom.client.ClientWorkspace} + * objects instead of common + * {@link com.sun.syndication.propono.atom.common.Workspace}s. + */ +public class ClientAtomService extends AtomService { + private static Log logger = LogFactory.getLog(ClientAtomService.class); + private String uri = null; + private HttpClient httpClient = null; + private AuthStrategy authStrategy = null; + + /** + * Create Atom blog service instance for specified URL and user account. + * @param url End-point URL of Atom service + */ + ClientAtomService(String uri, AuthStrategy authStrategy) + throws ProponoException { + this.uri = uri; + this.authStrategy = authStrategy; + Document doc = getAtomServiceDocument(); + parseAtomServiceDocument(doc); + } + + /** + * Get full entry from service by entry edit URI. + */ + public ClientEntry getEntry(String uri) throws ProponoException { + GetMethod method = new GetMethod(uri); + authStrategy.addAuthentication(httpClient, method); + try { + httpClient.executeMethod(method); + if (method.getStatusCode() != 200) { + throw new ProponoException("ERROR HTTP status code=" + method.getStatusCode()); + } + Entry romeEntry = Atom10Parser.parseEntry( + new InputStreamReader(method.getResponseBodyAsStream()), uri); + if (!romeEntry.isMediaEntry()) { + return new ClientEntry(this, null, romeEntry, false); + } else { + return new ClientMediaEntry(this, null, romeEntry, false); + } + } catch (Exception e) { + throw new ProponoException("ERROR: getting or parsing entry/media", e); + } finally { + method.releaseConnection(); + } + } + + void addAuthentication(HttpMethodBase method) throws ProponoException { + authStrategy.addAuthentication(httpClient, method); + } + + AuthStrategy getAuthStrategy() { + return authStrategy; + } + + private Document getAtomServiceDocument() throws ProponoException { + GetMethod method = null; + int code = -1; + try { + httpClient = new HttpClient(new MultiThreadedHttpConnectionManager()); + // TODO: make connection timeout configurable + httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(30000); + + method = new GetMethod(uri); + authStrategy.addAuthentication(httpClient, method); + httpClient.executeMethod(method); + + SAXBuilder builder = new SAXBuilder(); + return builder.build(method.getResponseBodyAsStream()); + + } catch (Throwable t) { + String msg = "ERROR retrieving Atom Service Document, code: "+code; + logger.debug(msg, t); + throw new ProponoException(msg, t); + } finally { + if (method != null) method.releaseConnection(); + } + } + + /** Deserialize an Atom service XML document into an object */ + private void parseAtomServiceDocument(Document document) throws ProponoException { + Element root = document.getRootElement(); + List spaces = root.getChildren("workspace", AtomService.ATOM_PROTOCOL); + Iterator iter = spaces.iterator(); + while (iter.hasNext()) { + Element e = (Element) iter.next(); + addWorkspace(new ClientWorkspace(e, this, uri)); + } + } + + /** + * Package access to httpClient. + */ + HttpClient getHttpClient() { + return httpClient; + } +} + diff --git a/src/main/java/com/sun/syndication/propono/atom/client/ClientCategories.java b/src/main/java/com/sun/syndication/propono/atom/client/ClientCategories.java new file mode 100644 index 0000000..965d1ca --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/ClientCategories.java @@ -0,0 +1,70 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. The ASF licenses this file to You +* under the Apache License, Version 2.0 (the "License"); you may not +* use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. For additional information regarding +* copyright in this work, please see the NOTICE file in the top level +* directory of this distribution. +*/ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.feed.atom.Category; +import com.sun.syndication.propono.atom.common.*; +import com.sun.syndication.propono.utils.ProponoException; +import java.io.IOException; +import java.io.InputStreamReader; +import org.apache.commons.httpclient.methods.GetMethod; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; + + +/** + * Models an Atom protocol Categories element, which may contain ROME Atom + * {@link com.sun.syndication.feed.atom.Category} elements. + */ +public class ClientCategories extends Categories { + private ClientCollection clientCollection = null; + + /** Load select from XML element */ + public ClientCategories(Element e, ClientCollection clientCollection) throws ProponoException { + this.clientCollection = clientCollection; + parseCategoriesElement(e); + if (getHref() != null) fetchContents(); + } + + public void fetchContents() throws ProponoException { + GetMethod method = new GetMethod(getHrefResolved()); + clientCollection.addAuthentication(method); + try { + clientCollection.getHttpClient().executeMethod(method); + if (method.getStatusCode() != 200) { + throw new ProponoException("ERROR HTTP status code=" + method.getStatusCode()); + } + SAXBuilder builder = new SAXBuilder(); + Document catsDoc = builder.build( + new InputStreamReader(method.getResponseBodyAsStream())); + parseCategoriesElement(catsDoc.getRootElement()); + + } catch (IOException ioe) { + throw new ProponoException( + "ERROR: reading out-of-line categories", ioe); + } catch (JDOMException jde) { + throw new ProponoException( + "ERROR: parsing out-of-line categories", jde); + } finally { + method.releaseConnection(); + } + } +} + diff --git a/src/main/java/com/sun/syndication/propono/atom/client/ClientCollection.java b/src/main/java/com/sun/syndication/propono/atom/client/ClientCollection.java new file mode 100644 index 0000000..94b270a --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/ClientCollection.java @@ -0,0 +1,217 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.io.InputStreamReader; +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.io.impl.Atom10Parser; +import com.sun.syndication.propono.atom.common.AtomService; +import com.sun.syndication.propono.atom.common.Categories; +import com.sun.syndication.propono.utils.ProponoException; +import com.sun.syndication.propono.atom.common.Collection; +import com.sun.syndication.propono.atom.common.Workspace; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.commons.httpclient.methods.GetMethod; +import org.jdom.Element; + +/** + * Models an Atom collection, extends Collection and adds methods for adding, + * retrieving, updateing and deleting entries. + */ +public class ClientCollection extends Collection { + static final Log logger = LogFactory.getLog(ClientCollection.class); + + private List categories = new ArrayList(); + private HttpClient httpClient = null; + private AuthStrategy authStrategy = null; + private boolean writable = true; + + private ClientWorkspace workspace = null; + private ClientAtomService service = null; + + ClientCollection(Element e, ClientWorkspace workspace, String baseURI) throws ProponoException { + super(e, baseURI); + this.workspace = workspace; + this.service = workspace.getAtomService(); + this.httpClient = workspace.getAtomService().getHttpClient(); + this.authStrategy = workspace.getAtomService().getAuthStrategy(); + parseCollectionElement(e); + } + + ClientCollection(String href, AuthStrategy authStrategy) throws ProponoException { + super("Standalone connection", "text", href); + this.authStrategy = authStrategy; + try { + httpClient = new HttpClient(new MultiThreadedHttpConnectionManager()); + // TODO: make connection timeout configurable + httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(30000); + } catch (Throwable t) { + throw new ProponoException("ERROR creating HTTPClient", t); + } + } + + void addAuthentication(HttpMethodBase method) throws ProponoException { + authStrategy.addAuthentication(httpClient, method); + } + + /** + * Package access to httpClient to allow use by ClientEntry and ClientMediaEntry. + */ + HttpClient getHttpClient() { + return httpClient; + } + + /** + * Get iterator over entries in this collection. Entries returned are + * considered to be partial entries cannot be saved/updated. + */ + public Iterator getEntries() throws ProponoException { + return new EntryIterator(this); + } + + /** + * Get full entry specified by entry edit URI. + * Note that entry may or may not be associated with this collection. + * @return ClientEntry or ClientMediaEntry specified by URI. + */ + public ClientEntry getEntry(String uri) throws ProponoException { + GetMethod method = new GetMethod(uri); + authStrategy.addAuthentication(httpClient, method); + try { + httpClient.executeMethod(method); + if (method.getStatusCode() != 200) { + throw new ProponoException("ERROR HTTP status code=" + method.getStatusCode()); + } + Entry romeEntry = Atom10Parser.parseEntry( + new InputStreamReader(method.getResponseBodyAsStream()), uri); + if (!romeEntry.isMediaEntry()) { + return new ClientEntry(service, this, romeEntry, false); + } else { + return new ClientMediaEntry(service, this, romeEntry, false); + } + } catch (Exception e) { + throw new ProponoException("ERROR: getting or parsing entry/media, HTTP code: ", e); + } finally { + method.releaseConnection(); + } + } + + /** + * Get workspace or null if collection is not associated with a workspace. + */ + public Workspace getWorkspace() { + return workspace; + } + + /** + * Determines if collection is writable. + */ + public boolean isWritable() { + return writable; + } + + /** + * Create new entry associated with collection, but do not save to server. + * @throws ProponoException if collecton is not writable. + */ + public ClientEntry createEntry() throws ProponoException { + if (!isWritable()) throw new ProponoException("Collection is not writable"); + return new ClientEntry(service, this); + } + + /** + * Create new media entry assocaited with collection, but do not save. + * server. Depending on the Atom server, you may or may not be able to + * persist the properties of the entry that is returned. + * @param title Title to used for uploaded file. + * @param slug String to be used in file-name of stored file + * @param contentType MIME content-type of file. + * @param bytes Data to be uploaded as byte array. + * @throws ProponoException if collecton is not writable + */ + public ClientMediaEntry createMediaEntry( + String title, String slug, String contentType, byte[] bytes) + throws ProponoException { + if (!isWritable()) throw new ProponoException("Collection is not writable"); + return new ClientMediaEntry(service, this, title, slug, contentType, bytes); + } + + /** + * Create new media entry assocaited with collection, but do not save. + * server. Depending on the Atom server, you may or may not be able to. + * persist the properties of the entry that is returned. + * @param title Title to used for uploaded file. + * @param slug String to be used in file-name of stored file + * @param contentType MIME content-type of file. + * @param is Data to be uploaded as InputStream. + * @throws ProponoException if collecton is not writable + */ + public ClientMediaEntry createMediaEntry( + String title, String slug, String contentType, InputStream is) + throws ProponoException { + if (!isWritable()) throw new ProponoException("Collection is not writable"); + return new ClientMediaEntry(service, this, title, slug, contentType, is); + } + + /** + * Save to collection a new entry that was created by a createEntry() + * or createMediaEntry() and save it to the server. + * @param entry Entry to be saved. + * @throws ProponoException on error, if collection is not writable or if entry is partial. + */ + public void addEntry(ClientEntry entry) throws ProponoException { + if (!isWritable()) throw new ProponoException("Collection is not writable"); + entry.addToCollection(this); + } + + protected void parseCollectionElement(Element element) throws ProponoException { + if (workspace == null) return; + + setHref(element.getAttribute("href").getValue()); + + Element titleElem = element.getChild("title", AtomService.ATOM_FORMAT); + if (titleElem != null) { + setTitle(titleElem.getText()); + if (titleElem.getAttribute("type", AtomService.ATOM_FORMAT) != null) { + setTitleType(titleElem.getAttribute("type", AtomService.ATOM_FORMAT).getValue()); + } + } + + List acceptElems = element.getChildren("accept", AtomService.ATOM_PROTOCOL); + if (acceptElems != null && acceptElems.size() > 0) { + for (Iterator it = acceptElems.iterator(); it.hasNext();) { + Element acceptElem = (Element)it.next(); + addAccept(acceptElem.getTextTrim()); + } + } + + // Loop to parse element to Categories objects + List catsElems = element.getChildren("categories", AtomService.ATOM_PROTOCOL); + for (Iterator catsIter = catsElems.iterator(); catsIter.hasNext();) { + Element catsElem = (Element) catsIter.next(); + Categories cats = new ClientCategories(catsElem, this); + addCategories(cats); + } + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/client/ClientEntry.java b/src/main/java/com/sun/syndication/propono/atom/client/ClientEntry.java new file mode 100644 index 0000000..d22f303 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/ClientEntry.java @@ -0,0 +1,259 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.feed.atom.Content; +import com.sun.syndication.feed.atom.Link; +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.io.impl.Atom10Generator; +import com.sun.syndication.io.impl.Atom10Parser; +import com.sun.syndication.propono.utils.ProponoException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import org.apache.commons.httpclient.methods.DeleteMethod; +import org.apache.commons.httpclient.methods.EntityEnclosingMethod; +import org.apache.commons.httpclient.methods.PutMethod; +import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.sun.syndication.propono.utils.Utilities; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.methods.PostMethod; + +/** + * Client implementation of Atom entry, extends ROME Entry to add methods for + * easily getting/setting content, updating and removing the entry from the server. + */ +public class ClientEntry extends Entry { + private static final Log logger = LogFactory.getLog(ClientEntry.class); + + boolean partial = false; + + private ClientAtomService service = null; + private ClientCollection collection = null; + + public ClientEntry(ClientAtomService service, ClientCollection collection) { + super(); + this.service = service; + this.collection = collection; + } + + public ClientEntry(ClientAtomService service, ClientCollection collection, + Entry entry, boolean partial) throws ProponoException { + super(); + this.service = service; + this.collection = collection; + this.partial = partial; + try { + BeanUtils.copyProperties(this, entry); + } catch (Exception e) { + throw new ProponoException("ERROR: copying fields from ROME entry", e); + } + } + + /** + * Set content of entry. + * @param contentString content string. + * @param type Must be "text" for plain text, "html" for escaped HTML, + * "xhtml" for XHTML or a valid MIME content-type. + */ + public void setContent(String contentString, String type) { + Content newContent = new Content(); + newContent.setType(type == null ? Content.HTML : type); + newContent.setValue(contentString); + ArrayList contents = new ArrayList(); + contents.add(newContent); + setContents(contents); + } + + /** + * Convenience method to set first content object in content collection. + * Atom 1.0 allows only one content element per entry. + */ + public void setContent(Content c) { + ArrayList contents = new ArrayList(); + contents.add(c); + setContents(contents); + } + + /** + * Convenience method to get first content object in content collection. + * Atom 1.0 allows only one content element per entry. + */ + public Content getContent() { + if (getContents() != null && getContents().size() > 0) { + Content c = (Content)getContents().get(0); + return c; + } + return null; + } + + /** + * Determines if entries are equal based on edit URI. + */ + public boolean equals(Object o) { + if (o instanceof ClientEntry) { + ClientEntry other = (ClientEntry)o; + if (other.getEditURI() != null && getEditURI() != null) { + return other.getEditURI().equals(getEditURI()); + } + } + return false; + } + + /** + * Update entry by posting new representation of entry to server. + * Note that you should not attempt to update entries that you get from + * iterating over a collection they may be "partial" entries. If you want + * to update an entry, you must get it via one of the getEntry() + * methods in + * {@link com.sun.syndication.propono.atom.common.Collection} or + * {@link com.sun.syndication.propono.atom.common.AtomService}. + * @throws ProponoException If entry is a "partial" entry. + */ + public void update() throws ProponoException { + if (partial) throw new ProponoException("ERROR: attempt to update partial entry"); + EntityEnclosingMethod method = new PutMethod(getEditURI()); + addAuthentication(method); + StringWriter sw = new StringWriter(); + int code = -1; + try { + Atom10Generator.serializeEntry(this, sw); + method.setRequestEntity(new StringRequestEntity(sw.toString())); + method.setRequestHeader( + "Content-type", "application/atom+xml; charset=utf-8"); + getHttpClient().executeMethod(method); + InputStream is = method.getResponseBodyAsStream(); + if (method.getStatusCode() != 200 && method.getStatusCode() != 201) { + throw new ProponoException( + "ERROR HTTP status=" + method.getStatusCode() + " : " + Utilities.streamToString(is)); + } + + } catch (Exception e) { + String msg = "ERROR: updating entry, HTTP code: " + code; + logger.debug(msg, e); + throw new ProponoException(msg, e); + } finally { + method.releaseConnection(); + } + } + + /** + * Remove entry from server. + */ + public void remove() throws ProponoException { + if (getEditURI() == null) { + throw new ProponoException("ERROR: cannot delete unsaved entry"); + } + DeleteMethod method = new DeleteMethod(getEditURI()); + addAuthentication(method); + try { + getHttpClient().executeMethod(method); + } catch (IOException ex) { + throw new ProponoException("ERROR: removing entry, HTTP code", ex); + } finally { + method.releaseConnection(); + } + } + + void setCollection(ClientCollection collection) { + this.collection = collection; + } + + ClientCollection getCollection() { + return collection; + } + + /** + * Get the URI that can be used to edit the entry via HTTP PUT or DELETE. + */ + public String getEditURI() { + for (int i=0; i 0) { + Content c = (Content)getContents().get(0); + if (c.getSrc() != null) { + return getResourceAsStream(); + } else if (inputStream != null) { + return inputStream; + } else if (bytes != null) { + return new ByteArrayInputStream(bytes); + } else { + throw new ProponoException("ERROR: no src URI or binary data to return"); + } + } + else { + throw new ProponoException("ERROR: no content found in entry"); + } + } + + private InputStream getResourceAsStream() throws ProponoException { + if (getEditURI() == null) { + throw new ProponoException("ERROR: not yet saved to server"); + } + GetMethod method = new GetMethod(((Content)getContents()).getSrc()); + try { + getCollection().getHttpClient().executeMethod(method); + if (method.getStatusCode() != 200) { + throw new ProponoException("ERROR HTTP status=" + method.getStatusCode()); + } + return method.getResponseBodyAsStream(); + } catch (IOException e) { + throw new ProponoException("ERROR: getting media entry", e); + } + } + + /** + * Update entry on server. + */ + public void update() throws ProponoException { + if (partial) throw new ProponoException("ERROR: attempt to update partial entry"); + EntityEnclosingMethod method = null; + Content updateContent = (Content)getContents().get(0); + try { + if (getMediaLinkURI() != null && getBytes() != null) { + // existing media entry and new file, so PUT file to edit-media URI + method = new PutMethod(getMediaLinkURI()); + if (inputStream != null) { + method.setRequestEntity(new InputStreamRequestEntity(inputStream)); + } else { + method.setRequestEntity(new InputStreamRequestEntity(new ByteArrayInputStream(getBytes()))); + } + + method.setRequestHeader("Content-type", updateContent.getType()); + } + else if (getEditURI() != null) { + // existing media entry and NO new file, so PUT entry to edit URI + method = new PutMethod(getEditURI()); + StringWriter sw = new StringWriter(); + Atom10Generator.serializeEntry(this, sw); + method.setRequestEntity(new StringRequestEntity(sw.toString())); + method.setRequestHeader( + "Content-type", "application/atom+xml; charset=utf8"); + } else { + throw new ProponoException("ERROR: media entry has no edit URI or media-link URI"); + } + this.getCollection().addAuthentication(method); + method.addRequestHeader("Title", getTitle()); + getCollection().getHttpClient().executeMethod(method); + if (inputStream != null) inputStream.close(); + InputStream is = method.getResponseBodyAsStream(); + if (method.getStatusCode() != 200 && method.getStatusCode() != 201) { + throw new ProponoException( + "ERROR HTTP status=" + method.getStatusCode() + " : " + Utilities.streamToString(is)); + } + + } catch (Exception e) { + throw new ProponoException("ERROR: saving media entry"); + } + if (method.getStatusCode() != 201) { + throw new ProponoException("ERROR HTTP status=" + method.getStatusCode()); + } + } + + /** Package access, to be called by DefaultClientCollection */ + void addToCollection(ClientCollection col) throws ProponoException { + setCollection(col); + EntityEnclosingMethod method = new PostMethod(col.getHrefResolved()); + getCollection().addAuthentication(method); + StringWriter sw = new StringWriter(); + boolean error = false; + try { + Content c = (Content)getContents().get(0); + if (inputStream != null) { + method.setRequestEntity(new InputStreamRequestEntity(inputStream)); + } else { + method.setRequestEntity(new InputStreamRequestEntity(new ByteArrayInputStream(getBytes()))); + } + method.setRequestHeader("Content-type", c.getType()); + method.setRequestHeader("Title", getTitle()); + method.setRequestHeader("Slug", getSlug()); + getCollection().getHttpClient().executeMethod(method); + if (inputStream != null) inputStream.close(); + InputStream is = method.getResponseBodyAsStream(); + if (method.getStatusCode() == 200 || method.getStatusCode() == 201) { + Entry romeEntry = Atom10Parser.parseEntry( + new InputStreamReader(is), col.getHrefResolved()); + BeanUtils.copyProperties(this, romeEntry); + + } else { + throw new ProponoException( + "ERROR HTTP status-code=" + method.getStatusCode() + + " status-line: " + method.getStatusLine()); + } + } catch (IOException ie) { + throw new ProponoException("ERROR: saving media entry", ie); + } catch (JDOMException je) { + throw new ProponoException("ERROR: saving media entry", je); + } catch (FeedException fe) { + throw new ProponoException("ERROR: saving media entry", fe); + } catch (IllegalAccessException ae) { + throw new ProponoException("ERROR: saving media entry", ae); + } catch (InvocationTargetException te) { + throw new ProponoException("ERROR: saving media entry", te); + } + Header locationHeader = method.getResponseHeader("Location"); + if (locationHeader == null) { + logger.warn("WARNING added entry, but no location header returned"); + } else if (getEditURI() == null) { + List links = getOtherLinks(); + Link link = new Link(); + link.setHref(locationHeader.getValue()); + link.setRel("edit"); + links.add(link); + setOtherLinks(links); + } + } + + /** Set string to be used in file name of new media resource on server. */ + public String getSlug() { + return slug; + } + + /** Get string to be used in file name of new media resource on server. */ + public void setSlug(String slug) { + this.slug = slug; + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/sun/syndication/propono/atom/client/ClientWorkspace.java b/src/main/java/com/sun/syndication/propono/atom/client/ClientWorkspace.java new file mode 100644 index 0000000..6377662 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/ClientWorkspace.java @@ -0,0 +1,66 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.propono.atom.common.AtomService; +import com.sun.syndication.propono.atom.common.Workspace; +import com.sun.syndication.propono.atom.common.Workspace; +import com.sun.syndication.propono.utils.ProponoException; +import java.util.Iterator; +import java.util.List; +import org.jdom.Element; + + +/** + * Represents Atom protocol workspace on client-side. + * It extends the common + * {@link com.sun.syndication.propono.atom.common.Workspace} + * to return + * {@link com.sun.syndication.propono.atom.client.ClientCollection} + * objects instead of common + * {@link com.sun.syndication.propono.atom.common.Collection}s. + */ +public class ClientWorkspace extends Workspace { + private ClientAtomService atomService = null; + + ClientWorkspace(Element e, ClientAtomService atomService, String baseURI) throws ProponoException { + super("dummy", "dummy"); + this.atomService = atomService; + parseWorkspaceElement(e, baseURI); + } + + /** + * Package access to parent service. + */ + ClientAtomService getAtomService() { + return atomService; + } + + /** Deserialize a Atom workspace XML element into an object */ + protected void parseWorkspaceElement(Element element, String baseURI) throws ProponoException { + Element titleElem = element.getChild("title", AtomService.ATOM_FORMAT); + setTitle(titleElem.getText()); + if (titleElem.getAttribute("type", AtomService.ATOM_FORMAT) != null) { + setTitleType(titleElem.getAttribute("type", AtomService.ATOM_FORMAT).getValue()); + } + List collections = element.getChildren("collection", AtomService.ATOM_PROTOCOL); + Iterator iter = collections.iterator(); + while (iter.hasNext()) { + Element e = (Element) iter.next(); + addCollection(new ClientCollection(e, this, baseURI)); + } + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/client/EntryIterator.java b/src/main/java/com/sun/syndication/propono/atom/client/EntryIterator.java new file mode 100644 index 0000000..5014d8f --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/EntryIterator.java @@ -0,0 +1,124 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.feed.atom.Feed; +import com.sun.syndication.feed.atom.Link; +import com.sun.syndication.io.WireFeedInput; +import com.sun.syndication.propono.utils.ProponoException; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jdom.Document; +import org.jdom.input.SAXBuilder; + + +/** + * Enables iteration over entries in Atom protocol collection. + */ +public class EntryIterator implements Iterator { + static final Log logger = LogFactory.getLog(EntryIterator.class); + private final ClientCollection collection; + + int maxEntries = 20; + int offset = 0; + Iterator members = null; + Feed col = null; + String collectionURI; + String nextURI; + + EntryIterator(ClientCollection collection) throws ProponoException { + this.collection = collection; + collectionURI = collection.getHrefResolved(); + nextURI = collectionURI; + getNextEntries(); + } + + /** + * Returns true if more entries are available. + */ + public boolean hasNext() { + if (!members.hasNext()) { + try { + getNextEntries(); + } catch (Exception ignored) { + logger.error("ERROR getting next entries", ignored); + } + } + return members.hasNext(); + } + + /** + * Get next entry in collection. + */ + public Object next() { + if (hasNext()) { + Entry romeEntry = (Entry)members.next(); + try { + if (!romeEntry.isMediaEntry()) { + return new ClientEntry(null, collection, romeEntry, true); + } else { + return new ClientMediaEntry(null, collection, romeEntry, true); + } + } catch (ProponoException e) { + throw new RuntimeException("Unexpected exception creating ClientEntry or ClientMedia", e); + } + } + throw new NoSuchElementException(); + } + + /** + * Remove entry is not implemented. + */ + public void remove() { + // optional method, not implemented + } + + private void getNextEntries() throws ProponoException { + if (nextURI == null) return; + GetMethod colGet = new GetMethod( collection.getHrefResolved(nextURI) ); + collection.addAuthentication(colGet); + try { + collection.getHttpClient().executeMethod(colGet); + SAXBuilder builder = new SAXBuilder(); + Document doc = builder.build(colGet.getResponseBodyAsStream()); + WireFeedInput feedInput = new WireFeedInput(); + col = (Feed) feedInput.build(doc); + } catch (Exception e) { + throw new ProponoException("ERROR: fetching or parsing next entries, HTTP code: " + (colGet != null ? colGet.getStatusCode() : -1), e); + } finally { + colGet.releaseConnection(); + } + members = col.getEntries().iterator(); + offset += col.getEntries().size(); + + nextURI = null; + List altLinks = col.getOtherLinks(); + if (altLinks != null) { + Iterator iter = altLinks.iterator(); + while (iter.hasNext()) { + Link link = (Link)iter.next(); + if ("next".equals(link.getRel())) { + nextURI = link.getHref(); + } + } + } + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/client/GDataAuthStrategy.java b/src/main/java/com/sun/syndication/propono/atom/client/GDataAuthStrategy.java new file mode 100644 index 0000000..987db9f --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/GDataAuthStrategy.java @@ -0,0 +1,67 @@ +/* + * Copyright 2009 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.propono.utils.ProponoException; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.methods.PostMethod; + + +public class GDataAuthStrategy implements AuthStrategy { + private String email; + private String password; + private String service; + private String authToken; + + public GDataAuthStrategy(String email, String password, String service) throws ProponoException { + this.email = email; + this.password = password; + this.service = service; + init(); + } + + private void init() throws ProponoException { + try { + HttpClient httpClient = new HttpClient(); + PostMethod method = new PostMethod("https://www.google.com/accounts/ClientLogin"); + NameValuePair[] data = { + new NameValuePair("Email", email), + new NameValuePair("Passwd", password), + new NameValuePair("accountType", "GOOGLE"), + new NameValuePair("service", service), + new NameValuePair("source", "ROME Propono Atompub Client") + }; + method.setRequestBody(data); + httpClient.executeMethod(method); + + String responseBody = method.getResponseBodyAsString(); + int authIndex = responseBody.indexOf("Auth="); + + authToken = "GoogleLogin auth=" + responseBody.trim().substring(authIndex + 5); + + } catch (Throwable t) { + t.printStackTrace(); + throw new ProponoException("ERROR obtaining Google authentication string", t); + } + } + + public void addAuthentication(HttpClient httpClient, HttpMethodBase method) throws ProponoException { + httpClient.getParams().setAuthenticationPreemptive(true); + method.setRequestHeader("Authorization", authToken); + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/client/NoAuthStrategy.java b/src/main/java/com/sun/syndication/propono/atom/client/NoAuthStrategy.java new file mode 100644 index 0000000..68fb129 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/NoAuthStrategy.java @@ -0,0 +1,31 @@ +/* + * Copyright 2009 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.propono.utils.ProponoException; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethodBase; + +/** + * No authentication + */ +public class NoAuthStrategy implements AuthStrategy { + + public void addAuthentication(HttpClient httpClient, HttpMethodBase method) throws ProponoException { + // no-op + } + +} diff --git a/src/main/java/com/sun/syndication/propono/atom/client/OAuthStrategy.java b/src/main/java/com/sun/syndication/propono/atom/client/OAuthStrategy.java new file mode 100644 index 0000000..56f3b0c --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/client/OAuthStrategy.java @@ -0,0 +1,302 @@ +/* + * Copyright 2009 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.propono.utils.ProponoException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import net.oauth.OAuth; +import net.oauth.OAuthAccessor; +import net.oauth.OAuthConsumer; +import net.oauth.OAuthMessage; +import net.oauth.OAuthServiceProvider; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpMethodBase; +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.util.ParameterParser; + + +/** + * Strategy for using OAuth. + */ +public class OAuthStrategy implements AuthStrategy { + + private State state = State.UNAUTHORIZED; + + private enum State { + UNAUTHORIZED, // have not sent any requests + REQUEST_TOKEN, // have a request token + AUTHORIZED, // are authorized + ACCESS_TOKEN // have access token, ready to make calls + } + + private String username; + private String consumerKey; + private String consumerSecret; + private String keyType; + + private String reqUrl; + private String authzUrl; + private String accessUrl; + + private String nonce; + private long timestamp; + + private String requestToken = null; + private String accessToken = null; + private String tokenSecret = null; + + /** + * Create OAuth authentcation strategy and negotiate with services to + * obtain access token to be used in subsequent calls. + * + * @param username Username to be used in authentication + * @param key Consumer key + * @param secret Consumer secret + * @param keyType Key type (e.g. "HMAC-SHA1") + * @param reqUrl URL of request token service + * @param authzUrl URL of authorize service + * @param accessUrl URL of acess token service + * @throws ProponoException on any sort of initialization error + */ + public OAuthStrategy( + String username, String key, String secret, String keyType, + String reqUrl, String authzUrl, String accessUrl) throws ProponoException { + + this.username = username; + this.reqUrl = reqUrl; + this.authzUrl = authzUrl; + this.accessUrl = accessUrl; + this.consumerKey = key; + this.consumerSecret = secret; + this.keyType = keyType; + + this.nonce = UUID.randomUUID().toString(); + this.timestamp = (long)(new Date().getTime()/1000L); + + init(); + } + + private void init() throws ProponoException { + callOAuthUri(reqUrl); + callOAuthUri(authzUrl); + callOAuthUri(accessUrl); + } + + public void addAuthentication(HttpClient httpClient, HttpMethodBase method) throws ProponoException { + + if (state != State.ACCESS_TOKEN) { + throw new ProponoException("ERROR: authentication strategy failed init"); + } + + // add OAuth name/values to request query string + + // wish we didn't have to parse them apart first, ugh + List originalqlist = null; + if (method.getQueryString() != null) { + String qstring = method.getQueryString().trim(); + qstring = qstring.startsWith("?") ? qstring.substring(1) : qstring; + originalqlist = new ParameterParser().parse(qstring, '&'); + } else { + originalqlist = new ArrayList(); + } + + // put query string into hashmap form to please OAuth.net classes + Map params = new HashMap(); + for (Iterator it = originalqlist.iterator(); it.hasNext();) { + NameValuePair pair = (NameValuePair)it.next(); + params.put(pair.getName(), pair.getValue()); + } + + // add OAuth params to query string + params.put("xoauth_requestor_id", username); + params.put("oauth_consumer_key", consumerKey); + params.put("oauth_signature_method", keyType); + params.put("oauth_timestamp", Long.toString(timestamp)); + params.put("oauth_nonce", nonce); + params.put("oauth_token", accessToken); + params.put("oauth_token_secret", tokenSecret); + + // sign complete URI + String finalUri = null; + OAuthServiceProvider provider = + new OAuthServiceProvider(reqUrl, authzUrl, accessUrl); + OAuthConsumer consumer = + new OAuthConsumer(null, consumerKey, consumerSecret, provider); + OAuthAccessor accessor = new OAuthAccessor(consumer); + accessor.tokenSecret = tokenSecret; + OAuthMessage message; + try { + message = new OAuthMessage( + method.getName(), method.getURI().toString(), params.entrySet()); + message.sign(accessor); + + finalUri = OAuth.addParameters(message.URL, message.getParameters()); + + } catch (Exception ex) { + throw new ProponoException("ERROR: OAuth signing request", ex); + } + + // pull query string off and put it back onto method + method.setQueryString(finalUri.substring(finalUri.lastIndexOf("?"))); + } + + + private void callOAuthUri(String uri) throws ProponoException { + + final HttpClient httpClient = new HttpClient(); + + final HttpMethodBase method; + final String content; + + Map params = new HashMap(); + if (params == null) { + params = new HashMap(); + } + params.put("oauth_version", "1.0"); + if (username != null) { + params.put("xoauth_requestor_id", username); + } + params.put("oauth_consumer_key", consumerKey); + params.put("oauth_signature_method", keyType); + params.put("oauth_timestamp", Long.toString(timestamp)); + params.put("oauth_nonce", nonce); + params.put("oauth_callback", "none"); + + OAuthServiceProvider provider = + new OAuthServiceProvider(reqUrl, authzUrl, accessUrl); + OAuthConsumer consumer = + new OAuthConsumer(null, consumerKey, consumerSecret, provider); + OAuthAccessor accessor = new OAuthAccessor(consumer); + + if (state == State.UNAUTHORIZED) { + + try { + OAuthMessage message = new OAuthMessage("GET", uri, params.entrySet()); + message.sign(accessor); + + String finalUri = OAuth.addParameters(message.URL, message.getParameters()); + method = new GetMethod(finalUri); + httpClient.executeMethod(method); + content = method.getResponseBodyAsString(); + + } catch (Exception e) { + throw new ProponoException("ERROR fetching request token", e); + } + + } else if (state == State.REQUEST_TOKEN) { + + try { + params.put("oauth_token", requestToken); + params.put("oauth_token_secret", tokenSecret); + accessor.tokenSecret = tokenSecret; + + OAuthMessage message = new OAuthMessage("POST", uri, params.entrySet()); + message.sign(accessor); + + String finalUri = OAuth.addParameters(message.URL, message.getParameters()); + method = new PostMethod(finalUri); + httpClient.executeMethod(method); + content = method.getResponseBodyAsString(); + + } catch (Exception e) { + throw new ProponoException("ERROR fetching request token", e); + } + + } else if (state == State.AUTHORIZED) { + + try { + params.put("oauth_token", accessToken); + params.put("oauth_token_secret", tokenSecret); + accessor.tokenSecret = tokenSecret; + + OAuthMessage message = new OAuthMessage("GET", uri, params.entrySet()); + message.sign(accessor); + + String finalUri = OAuth.addParameters(message.URL, message.getParameters()); + method = new GetMethod(finalUri); + httpClient.executeMethod(method); + content = method.getResponseBodyAsString(); + + } catch (Exception e) { + throw new ProponoException("ERROR fetching request token", e); + } + + } else { + method = null; + content = null; + return; + } + + + String token = null; + String secret = null; + + if (content != null) { + String[] settings = content.split("&"); + for (int i=0; i 1) { + if ("oauth_token".equals(setting[0])) { + token = setting[1]; + } else if ("oauth_token_secret".equals(setting[0])) { + secret = setting[1]; + } + } + } + } + + switch (state) { + + case UNAUTHORIZED: + if (token != null && secret != null) { + requestToken = token; + tokenSecret = secret; + state = State.REQUEST_TOKEN; + } else { + throw new ProponoException("ERROR: requestToken or tokenSecret is null"); + } + break; + + case REQUEST_TOKEN: + if (method.getStatusCode() == 200) { + state = State.AUTHORIZED; + } else { + throw new ProponoException("ERROR: authorization returned code: " + method.getStatusCode()); + } + break; + + case AUTHORIZED: + if (token != null && secret != null) { + accessToken = token; + tokenSecret = secret; + state = State.ACCESS_TOKEN; + } else { + throw new ProponoException("ERROR: accessToken or tokenSecret is null"); + } + break; + } + } +} + + diff --git a/src/main/java/com/sun/syndication/propono/atom/client/atomclient-diagram.gif b/src/main/java/com/sun/syndication/propono/atom/client/atomclient-diagram.gif new file mode 100644 index 0000000000000000000000000000000000000000..21b109493156d9d46d41e44dc7c0e3a859a74389 GIT binary patch literal 19120 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 elements + for (Iterator catIter = cats.getCategories().iterator(); catIter.hasNext();) { + Category cat = (Category) catIter.next(); + Element catElem = new Element("category", AtomService.ATOM_FORMAT); + catElem.setAttribute("term", cat.getTerm(), AtomService.ATOM_FORMAT); + if (cat.getScheme() != null) { // optional + catElem.setAttribute("scheme", cat.getScheme(), AtomService.ATOM_FORMAT); + } + if (cat.getLabel() != null) { // optional + catElem.setAttribute("label", cat.getLabel(), AtomService.ATOM_FORMAT); + } + catsElem.addContent(catElem); + } + } + return catsElem; + } + + protected void parseCategoriesElement(Element catsElem) { + if (catsElem.getAttribute("href", AtomService.ATOM_PROTOCOL) != null) { + setHref(catsElem.getAttribute("href", AtomService.ATOM_PROTOCOL).getValue()); + } + if (catsElem.getAttribute("fixed", AtomService.ATOM_PROTOCOL) != null) { + if ("yes".equals(catsElem.getAttribute("fixed", AtomService.ATOM_PROTOCOL).getValue())) { + setFixed(true); + } + } + if (catsElem.getAttribute("scheme", AtomService.ATOM_PROTOCOL) != null) { + setScheme(catsElem.getAttribute("scheme", AtomService.ATOM_PROTOCOL).getValue()); + } + // Loop to parse elemenents to Category objects + List catElems = catsElem.getChildren("category", AtomService.ATOM_FORMAT); + for (Iterator catIter = catElems.iterator(); catIter.hasNext();) { + Element catElem = (Element) catIter.next(); + Category cat = new Category(); + cat.setTerm(catElem.getAttributeValue("term", AtomService.ATOM_FORMAT)); + cat.setLabel(catElem.getAttributeValue("label", AtomService.ATOM_FORMAT)); + cat.setScheme(catElem.getAttributeValue("scheme", AtomService.ATOM_FORMAT)); + addCategory(cat); + } + } +} + diff --git a/src/main/java/com/sun/syndication/propono/atom/common/Collection.java b/src/main/java/com/sun/syndication/propono/atom/common/Collection.java new file mode 100644 index 0000000..0e4f5e7 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/common/Collection.java @@ -0,0 +1,252 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. The ASF licenses this file to You +* under the Apache License, Version 2.0 (the "License"); you may not +* use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. For additional information regarding +* copyright in this work, please see the NOTICE file in the top level +* directory of this distribution. +*/ +package com.sun.syndication.propono.atom.common; + +import com.sun.syndication.io.impl.Atom10Parser; +import com.sun.syndication.propono.utils.ProponoException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.jdom.Element; + + +/** + * Models an Atom workspace collection. + */ +public class Collection { + + public static final String ENTRY_TYPE = "application/atom+xml;type=entry"; + + private Element collectionElement = null; + private String baseURI = null; + private String title = null; + private String titleType = null; // may be TEXT, HTML, XHTML + private List accepts = new ArrayList(); // of Strings + private String listTemplate = null; + private String href = null; + private List categories = new ArrayList(); // of Categories objects + + /** + * Collection MUST have title and href. + * @param title Title for collection + * @param titleType Content type of title (null for plain text) + * @param href Collection URI. + */ + public Collection(String title, String titleType, String href) { + this.title = title; + this.titleType = titleType; + this.href = href; + } + + /** Load self from XML element */ + public Collection(Element e) throws ProponoException { + this.collectionElement = e; + this.parseCollectionElement(e); + } + + /** Load self from XML element and base URI for resolving relative URIs */ + public Collection(Element e, String baseURI) throws ProponoException { + this.collectionElement = e; + this.baseURI = baseURI; + this.parseCollectionElement(e); + } + + /** + * List of content-type ranges accepted by collection. + */ + public List getAccepts() { + return accepts; + } + + public void addAccept(String accept) { + this.accepts.add(accept); + } + + public void setAccepts(List accepts) { + this.accepts = accepts; + } + + /** The URI of the collection */ + public String getHref() { + return href; + } + + /** + * Set URI of collection + */ + public void setHref(String href) { + this.href = href; + } + + /** Get resolved URI of the collection, or null if impossible to determine */ + public String getHrefResolved() { + if (Atom10Parser.isAbsoluteURI(href)) { + return href; + } else if (baseURI != null && collectionElement != null) { + int lastslash = baseURI.lastIndexOf("/"); + return Atom10Parser.resolveURI(baseURI.substring(0, lastslash), collectionElement, href); + } + return null; + } + + /** Get resolved URI using collection's baseURI, or null if impossible to determine */ + public String getHrefResolved(String relativeUri) { + if (Atom10Parser.isAbsoluteURI(relativeUri)) { + return relativeUri; + } else if (baseURI != null && collectionElement != null) { + int lastslash = baseURI.lastIndexOf("/"); + return Atom10Parser.resolveURI(baseURI.substring(0, lastslash), collectionElement, relativeUri); + } + return null; + } + + /** Must have human readable title */ + public String getTitle() { + return title; + } + + /** + * Set title of collection. + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Type of title ("text", "html" or "xhtml") + */ + public String getTitleType() { + return titleType; + } + + /** + * Type of title ("text", "html" or "xhtml") + */ + public void setTitleType(String titleType) { + this.titleType = titleType; + } + + /** Workspace can have multiple Categories objects */ + public void addCategories(Categories cats) { + categories.add(cats); + } + + /** + * Get categories allowed by collection. + * @return Collection of {@link com.sun.syndication.propono.atom.common.Categories} objects. + */ + public List getCategories() { + return categories; + } + + /** + * Returns true if contentType is accepted by collection. + */ + public boolean accepts(String ct) { + for (Iterator it = accepts.iterator(); it.hasNext();) { + String accept = (String)it.next(); + if (accept != null && accept.trim().equals("*/*")) return true; + String entryType = "application/atom+xml"; + boolean entry = entryType.equals(ct); + if (entry && null == accept) { + return true; + } else if (entry && "entry".equals(accept)) { + return true; + } else if (entry && entryType.equals(accept)) { + return true; + } else { + String[] rules = (String[])accepts.toArray(new String[accepts.size()]); + for (int i=0; i 0) { + rule = rule.substring(0, slashstar + 1); + if (ct.startsWith(rule)) return true; + } + } + } + } + return false; + } + + /** + * Serialize an AtomService.Collection into an XML element + */ + public Element collectionToElement() { + Collection collection = this; + Element element = new Element("collection", AtomService.ATOM_PROTOCOL); + element.setAttribute("href", collection.getHref()); + + Element titleElem = new Element("title", AtomService.ATOM_FORMAT); + titleElem.setText(collection.getTitle()); + if (collection.getTitleType() != null && !collection.getTitleType().equals("TEXT")) { + titleElem.setAttribute("type", collection.getTitleType(), AtomService.ATOM_FORMAT); + } + element.addContent(titleElem); + + // Loop to create elements + for (Iterator it = collection.getCategories().iterator(); it.hasNext();) { + Categories cats = (Categories)it.next(); + element.addContent(cats.categoriesToElement()); + } + + for (Iterator it = collection.getAccepts().iterator(); it.hasNext();) { + String range = (String)it.next(); + Element acceptElem = new Element("accept", AtomService.ATOM_PROTOCOL); + acceptElem.setText(range); + element.addContent(acceptElem); + } + + return element; + } + + /** Deserialize an Atom service collection XML element into an object */ + public Collection elementToCollection(Element element) throws ProponoException { + return new Collection(element); + } + + protected void parseCollectionElement(Element element) throws ProponoException { + setHref(element.getAttribute("href").getValue()); + + Element titleElem = element.getChild("title", AtomService.ATOM_FORMAT); + if (titleElem != null) { + setTitle(titleElem.getText()); + if (titleElem.getAttribute("type", AtomService.ATOM_FORMAT) != null) { + setTitleType(titleElem.getAttribute("type", AtomService.ATOM_FORMAT).getValue()); + } + } + + List acceptElems = element.getChildren("accept", AtomService.ATOM_PROTOCOL); + if (acceptElems != null && acceptElems.size() > 0) { + for (Iterator it = acceptElems.iterator(); it.hasNext();) { + Element acceptElem = (Element)it.next(); + addAccept(acceptElem.getTextTrim()); + } + } + + // Loop to parse element to Categories objects + List catsElems = element.getChildren("categories", AtomService.ATOM_PROTOCOL); + for (Iterator catsIter = catsElems.iterator(); catsIter.hasNext();) { + Element catsElem = (Element) catsIter.next(); + Categories cats = new Categories(catsElem, baseURI); + addCategories(cats); + } + } +} + diff --git a/src/main/java/com/sun/syndication/propono/atom/common/Workspace.java b/src/main/java/com/sun/syndication/propono/atom/common/Workspace.java new file mode 100644 index 0000000..b15dbee --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/common/Workspace.java @@ -0,0 +1,147 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. The ASF licenses this file to You +* under the Apache License, Version 2.0 (the "License"); you may not +* use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. For additional information regarding +* copyright in this work, please see the NOTICE file in the top level +* directory of this distribution. +*/ +package com.sun.syndication.propono.atom.common; + +import com.sun.syndication.propono.utils.ProponoException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.jdom.Element; + + +/** + * Models an Atom workspace. + */ +public class Workspace { + private String title = null; + private String titleType = null; // may be TEXT, HTML, XHTML + private List collections = new ArrayList(); + + /** + * Collection MUST have title. + * @param title Title for collection + * @param titleType Content type of title (null for plain text) + */ + public Workspace(String title, String titleType) { + this.title = title; + this.titleType = titleType; + } + + public Workspace(Element elem) throws ProponoException { + parseWorkspaceElement(elem); + } + + /** Iterate over collections in workspace */ + public List getCollections() { + return collections; + } + + /** Add new collection to workspace */ + public void addCollection(Collection col) { + collections.add(col); + } + + /** + * DefaultWorkspace must have a human readable title + */ + public String getTitle() { + return title; + } + + /** + * Set title of workspace. + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Get title type ("text", "html", "xhtml" or MIME content-type) + */ + public String getTitleType() { + return titleType; + } + + /** + * Set title type ("text", "html", "xhtml" or MIME content-type) + */ + public void setTitleType(String titleType) { + this.titleType = titleType; + } + + /** + * Find collection by title and/or content-type. + * @param title Title or null to match all titles. + * @param contentType Content-type or null to match all content-types. + * @return First Collection that matches title and/or content-type. + */ + public Collection findCollection(String title, String contentType) { + for (Iterator it = collections.iterator(); it.hasNext();) { + Collection col = (Collection) it.next(); + if (title != null && col.accepts(contentType)) { + return col; + } else if (col.accepts(contentType)) { + return col; + } + } + return null; + } + + /** Deserialize a Atom workspace XML element into an object */ + public static Workspace elementToWorkspace(Element element) throws ProponoException { + return new Workspace(element); + } + + /** + * Serialize an AtomService.DefaultWorkspace object into an XML element + */ + public Element workspaceToElement() { + Workspace space = this; + + Element element = new Element("workspace", AtomService.ATOM_PROTOCOL); + + Element titleElem = new Element("title", AtomService.ATOM_FORMAT); + titleElem.setText(space.getTitle()); + if (space.getTitleType() != null && !space.getTitleType().equals("TEXT")) { + titleElem.setAttribute("type", space.getTitleType(), AtomService.ATOM_FORMAT); + } + element.addContent(titleElem); + + Iterator iter = space.getCollections().iterator(); + while (iter.hasNext()) { + Collection col = (Collection) iter.next(); + element.addContent(col.collectionToElement()); + } + return element; + } + + /** Deserialize a Atom workspace XML element into an object */ + protected void parseWorkspaceElement(Element element) throws ProponoException { + Element titleElem = element.getChild("title", AtomService.ATOM_FORMAT); + setTitle(titleElem.getText()); + if (titleElem.getAttribute("type", AtomService.ATOM_FORMAT) != null) { + setTitleType(titleElem.getAttribute("type", AtomService.ATOM_FORMAT).getValue()); + } + List collections = element.getChildren("collection", AtomService.ATOM_PROTOCOL); + Iterator iter = collections.iterator(); + while (iter.hasNext()) { + Element e = (Element) iter.next(); + addCollection(new Collection(e)); + } + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModule.java b/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModule.java new file mode 100644 index 0000000..495ee58 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModule.java @@ -0,0 +1,42 @@ +/* + * Copyright 2007 Apache Software Foundation + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.common.rome; + +import com.sun.syndication.feed.module.Module; +import java.util.Date; + +/** + * ROME Extension Module to Atom protocol extensions to Atom format. + */ +public interface AppModule extends Module { + public static final String URI = "http://www.w3.org/2007/app"; + + /** True if entry is a draft */ + public Boolean getDraft(); + + /** Set to true if entry is a draft */ + public void setDraft(Boolean draft); + + /** Time of last edit */ + public Date getEdited(); + + /** Set time of last edit */ + public void setEdited(Date edited); +} diff --git a/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleGenerator.java b/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleGenerator.java new file mode 100644 index 0000000..c182609 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleGenerator.java @@ -0,0 +1,85 @@ +/* + * Copyright 2007 Apache Software Foundation + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.common.rome; + +import com.sun.syndication.io.impl.DateParser; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.jdom.Element; +import org.jdom.Namespace; + +import com.sun.syndication.feed.module.Module; +import com.sun.syndication.io.ModuleGenerator; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.TimeZone; + +/** + * Creates JDOM representation for APP Extension Module. + */ +public class AppModuleGenerator implements ModuleGenerator { + private static final Namespace APP_NS = + Namespace.getNamespace("app", AppModule.URI); + + public String getNamespaceUri() { + return AppModule.URI; + } + + private static final Set NAMESPACES; + + static { + Set nss = new HashSet(); + nss.add(APP_NS); + NAMESPACES = Collections.unmodifiableSet(nss); + } + + /** Get namespaces associated with this module */ + public Set getNamespaces() { + return NAMESPACES; + } + + /** Generate JDOM element for module and add it to parent element */ + public void generate(Module module, Element parent) { + AppModule m = (AppModule)module; + + if (m.getDraft() != null) { + String draft = m.getDraft().booleanValue() ? "yes" : "no"; + Element control = new Element("control", APP_NS); + control.addContent(generateSimpleElement("draft", draft)); + parent.addContent(control); + } + if (m.getEdited() != null) { + Element edited = new Element("edited", APP_NS); + // Inclulde millis in date/time + SimpleDateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); + dateFormater.setTimeZone(TimeZone.getTimeZone("GMT")); + edited.addContent(dateFormater.format(m.getEdited())); + parent.addContent(edited); + } + } + + private Element generateSimpleElement(String name, String value) { + Element element = new Element(name, APP_NS); + element.addContent(value); + return element; + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleImpl.java b/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleImpl.java new file mode 100644 index 0000000..e6485a5 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleImpl.java @@ -0,0 +1,69 @@ +/* + * Copyright 2007 Apache Software Foundation + * Copyright 2011 The ROME Teams + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.common.rome; + +import com.sun.syndication.feed.CopyFrom; +import com.sun.syndication.feed.module.ModuleImpl; +import java.util.Date; + +/** + * Bean representation of APP module. + */ +public class AppModuleImpl extends ModuleImpl implements AppModule { + private boolean draft = false; + private Date edited = null; + + public AppModuleImpl() { + super(AppModule.class, AppModule.URI); + } + + /** True if entry is draft */ + public Boolean getDraft() { + return draft ? Boolean.TRUE : Boolean.FALSE; + } + + /** Set to true if entry is draft */ + public void setDraft(Boolean draft) { + this.draft = draft.booleanValue(); + } + + /** Time of last edit */ + public Date getEdited() { + return edited; + } + + /** Set time of last edit */ + public void setEdited(Date edited) { + this.edited = edited; + } + + /** Get interface class of module */ + public Class getInterface() { + return AppModule.class; + } + + /** Copy from other module */ + public void copyFrom(CopyFrom obj) { + AppModule m = (AppModule)obj; + setDraft(m.getDraft()); + setEdited(m.getEdited()); + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleParser.java b/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleParser.java new file mode 100644 index 0000000..13b34c4 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/common/rome/AppModuleParser.java @@ -0,0 +1,66 @@ +/* + * Copyright 2007 Apache Software Foundation + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.common.rome; + +import com.sun.syndication.io.impl.DateParser; +import org.jdom.Element; +import org.jdom.Namespace; + +import com.sun.syndication.feed.module.Module; +import com.sun.syndication.io.ModuleParser; + +/** + * Parses APP module information from a JDOM element and into + * AppModule form. + */ +public class AppModuleParser implements ModuleParser { + + /** Get URI of module namespace */ + public String getNamespaceUri() { + return AppModule.URI; + } + + /** Get namespace of module */ + public Namespace getContentNamespace() { + return Namespace.getNamespace(AppModule.URI); + } + + /** Parse JDOM element into module */ + public Module parse(Element elem) { + boolean foundSomething = false; + AppModule m = new AppModuleImpl(); + Element control = elem.getChild("control", getContentNamespace()); + if (control != null) { + Element draftElem = control.getChild("draft", getContentNamespace()); + if (draftElem != null) { + if ("yes".equals(draftElem.getText())) m.setDraft(Boolean.TRUE); + if ("no".equals(draftElem.getText())) m.setDraft(Boolean.FALSE); + } + } + Element edited = elem.getChild("edited", getContentNamespace()); + if (edited != null) { + try { + m.setEdited(DateParser.parseW3CDateTime(edited.getTextTrim())); + } catch (Exception ignored) {} + } + return m; + } +} + diff --git a/src/main/java/com/sun/syndication/propono/atom/server/AtomException.java b/src/main/java/com/sun/syndication/propono/atom/server/AtomException.java new file mode 100644 index 0000000..93a8543 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/AtomException.java @@ -0,0 +1,53 @@ +/* + * Copyright 2007 Apache Software Foundation + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.server; + +import javax.servlet.http.HttpServletResponse; + + +/** + * Exception thrown by {@link com.sun.syndication.propono.atom.server.AtomHandler} + * and extended by other Propono Atom exception classes. + */ +public class AtomException extends Exception { + /** Construct new exception */ + public AtomException() { + super(); + } + /** Construct new exception with message */ + public AtomException(String msg) { + super(msg); + } + /** Contruct new exception with message and wrapping existing exception */ + public AtomException(String msg, Throwable t) { + super(msg, t); + } + /** Construct new exception to wrap existing one. */ + public AtomException(Throwable t) { + super(t); + } + /* Get HTTP status code associated with exception (HTTP 500 server error) */ + /** + * Get HTTP status associated with exception. + */ + public int getStatus() { + return HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/AtomHandler.java b/src/main/java/com/sun/syndication/propono/atom/server/AtomHandler.java new file mode 100644 index 0000000..8561073 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/AtomHandler.java @@ -0,0 +1,144 @@ +/* + * Copyright 2007 Apache Software Foundation + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.server; + +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.feed.atom.Feed; +import com.sun.syndication.propono.atom.common.AtomService; +import com.sun.syndication.propono.atom.common.Categories; + +/** + * Interface for handling single Atom protocol requests. + * + *

To create your own Atom protocol implementation you must implement this + * interface and create a concrete sub-class of + * {@link com.sun.syndication.propono.atom.server.AtomHandlerFactory} + * which is capable of instantiating it.

+ */ +public interface AtomHandler +{ + /** + * Get username of authenticated user. Return the username of the + * authenticated user + */ + public String getAuthenticatedUsername(); + + /** + * Return + * {@link com.sun.syndication.propono.atom.common.AtomService} + * object that contains the + * {@link com.sun.syndication.propono.atom.common.Workspace} objects + * available to the currently authenticated user and within those the + * {@link com.sun.syndication.propono.atom.common.Collection} avalaible. + */ + public AtomService getAtomService(AtomRequest req) throws AtomException; + + /** + * Get categories, a list of Categories objects + */ + public Categories getCategories(AtomRequest req) throws AtomException; + + /** + * Return collection or portion of collection specified by request. + * @param req Details of HTTP request + */ + public Feed getCollection(AtomRequest req) throws AtomException; + + /** + * Store new entry in collection specified by request and return + * representation of entry as it is stored on server. + * @param req Details of HTTP request + * @return Location URL of new entry + */ + public Entry postEntry(AtomRequest req, Entry entry) throws AtomException; + + /** + * Get entry specified by request. + * @param req Details of HTTP request + */ + public Entry getEntry(AtomRequest req) throws AtomException; + + /** + * Get media resource specified by request. + * @param req Details of HTTP request + */ + public AtomMediaResource getMediaResource(AtomRequest req) throws AtomException; + + /** + * Update entry specified by request and return new entry as represented + * on the server. + * @param req Details of HTTP request + */ + public void putEntry(AtomRequest req, Entry entry) throws AtomException; + + + /** + * Delete entry specified by request. + * @param req Details of HTTP request + */ + public void deleteEntry(AtomRequest req) throws AtomException; + + /** + * Store media data in collection specified by request, create an Atom + * media-link entry to store metadata for the new media file and return + * that entry to the caller. + * @param req Details of HTTP request + * @param entry New entry initialzied with only title and content type + * @return Location URL of new media entry + */ + public Entry postMedia(AtomRequest req, Entry entry) throws AtomException; + + /** + * Update the media file part of a media-link entry. + * @param req Details of HTTP request + */ + public void putMedia(AtomRequest req) throws AtomException; + + /** + * Return true if specified request represents URI of a Service Document. + * @param req Details of HTTP request + */ + public boolean isAtomServiceURI(AtomRequest req); + + /** + * Return true if specified request represents URI of a Categories Document. + * @param req Details of HTTP request + */ + public boolean isCategoriesURI(AtomRequest req); + + /** + * Return true if specified request represents URI of a collection. + * @param req Details of HTTP request + */ + public boolean isCollectionURI(AtomRequest req); + + /** + * Return true if specified request represents URI of an Atom entry. + * @param req Details of HTTP request + */ + public boolean isEntryURI(AtomRequest req); + + /** + * Return true if specified patrequesthinfo represents media-edit URI. + * @param req Details of HTTP request + */ + public boolean isMediaEditURI(AtomRequest req); +} + diff --git a/src/main/java/com/sun/syndication/propono/atom/server/AtomHandlerFactory.java b/src/main/java/com/sun/syndication/propono/atom/server/AtomHandlerFactory.java new file mode 100644 index 0000000..ca5fc55 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/AtomHandlerFactory.java @@ -0,0 +1,113 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.server; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Defines a factory that enables the + * {@link com.sun.syndication.propono.atom.server.AtomServlet} to obtain an + * {@link com.sun.syndication.propono.atom.server.AtomHandler} that handles an Atom request. + * + *

To create your own Atom protocol implementation you must sub-class this + * class with your own factory that is capable of creating instances of your + * {@link com.sun.syndication.propono.atom.server.AtomHandler} impementation.

+ */ +public abstract class AtomHandlerFactory { + + private static Log log = + LogFactory.getFactory().getInstance(AtomHandlerFactory.class); + + private static final String DEFAULT_PROPERTY_NAME = + "com.sun.syndication.propono.atom.server.AtomHandlerFactory"; + + private static final String FALLBACK_IMPL_NAME = + "com.sun.syndication.propono.atom.server.impl.FileBasedAtomHandlerFactory"; + + /* + *

Protected constructor to prevent instantiation. + * Use {@link #newInstance()}.

+ */ + protected AtomHandlerFactory() { + } + + /** + * Obtain a new instance of a AtomHandlerFactory. This static + * method creates a new factory instance. This method uses the following + * ordered lookup procedure to determine the AtomHandlerFactory + * implementation class to load: + *
    + *
  • + * Use the com.sun.syndication.propono.atom.server.AtomHandlerFactory + * system property. + *
  • + *
  • + * Use the properties file "/propono.properties" in the classpath. + * This configuration file is in standard java.util.Properties + * format and contains the fully qualified name of the implementation + * class with the key being the system property defined above. + * + * The propono.properties file is read only once by Propono and it's + * values are then cached for future use. If the file does not exist + * when the first attempt is made to read from it, no further attempts + * are made to check for its existence. It is not possible to change + * the value of any property in propono.properties after it has been + * read for the first time. + *
  • + *
  • + * If not available, to determine the classname. The Services API will look + * for a classname in the file: + * META-INF/services/com.sun.syndication.AtomHandlerFactory + * in jars available to the runtime. + *
  • + *
  • + * Platform default AtomHandlerFactory instance. + *
  • + *
+ * + * Once an application has obtained a reference to a AtomHandlerFactory + * it can use the factory to configure and obtain parser instances. + * + * @return New instance of a AtomHandlerFactory + * + * @throws FactoryConfigurationError if the implementation is not available + * or cannot be instantiated. + */ + public static AtomHandlerFactory newInstance() { + try { + return (AtomHandlerFactory) + FactoryFinder.find(DEFAULT_PROPERTY_NAME, FALLBACK_IMPL_NAME); + } catch (FactoryFinder.ConfigurationError e) { + log.error("ERROR: finding factory", e); + throw new FactoryConfigurationError(e.getException(), e.getMessage()); + } + } + + /** + * Creates a new instance of a {@link com.sun.syndication.propono.atom.server.AtomHandler} + * using the currently configured parameters. + * + * @return A new instance of a AtomHandler. + * + * @throws AtomConfigurationException if a AtomHandler cannot be created + * which satisfies the configuration requested. + */ + public abstract AtomHandler newAtomHandler( + HttpServletRequest req, HttpServletResponse res); +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/AtomMediaResource.java b/src/main/java/com/sun/syndication/propono/atom/server/AtomMediaResource.java new file mode 100644 index 0000000..b9c1882 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/AtomMediaResource.java @@ -0,0 +1,95 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.sun.syndication.propono.atom.server; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Date; +import javax.activation.FileTypeMap; +import javax.activation.MimetypesFileTypeMap; + +/** + * Represents a media link entry. + */ +public class AtomMediaResource { + private String contentType = null; + private long contentLength = 0; + private InputStream inputStream = null; + private Date lastModified = null; + private static FileTypeMap map = null; + + static { + // TODO: figure out why PNG is missing from Java MIME types + map = FileTypeMap.getDefaultFileTypeMap(); + if (map instanceof MimetypesFileTypeMap) { + try { + ((MimetypesFileTypeMap)map).addMimeTypes("image/png png PNG"); + } catch (Exception ignored) {} + } + } + + public AtomMediaResource(File resource) throws FileNotFoundException { + contentType = map.getContentType(resource.getName()); + contentLength = resource.length(); + lastModified = new Date(resource.lastModified()); + inputStream = new FileInputStream(resource); + } + + public AtomMediaResource(String name, long length, Date lastModified, InputStream is) + throws FileNotFoundException { + this.contentType = map.getContentType(name); + this.contentLength = length; + this.lastModified = lastModified; + this.inputStream = is; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public long getContentLength() { + return contentLength; + } + + public void setContentLength(long contentLength) { + this.contentLength = contentLength; + } + + public InputStream getInputStream() { + return inputStream; + } + + public void setInputStream(InputStream inputStream) { + this.inputStream = inputStream; + } + + public Date getLastModified() { + return lastModified; + } + + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + + +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/AtomNotAuthorizedException.java b/src/main/java/com/sun/syndication/propono/atom/server/AtomNotAuthorizedException.java new file mode 100644 index 0000000..6278b76 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/AtomNotAuthorizedException.java @@ -0,0 +1,49 @@ +/* + * Copyright 2007 Apache Software Foundation + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.server; + +import javax.servlet.http.HttpServletResponse; + +/** + * Exception to be thrown by AtomHandler implementations in the + * case that a user is not authorized to access a resource. + */ +public class AtomNotAuthorizedException extends AtomException { + /** Construct new exception */ + public AtomNotAuthorizedException() { + super(); + } + /** Construct new exception with message */ + public AtomNotAuthorizedException(String msg) { + super(msg); + } + /** Construct new exception with message and root cause */ + public AtomNotAuthorizedException(String msg, Throwable t) { + super(msg, t); + } + /** Construct new exception to wrap root cause*/ + public AtomNotAuthorizedException(Throwable t) { + super(t); + } + /** Get HTTP status code of exception (HTTP 403 unauthorized) */ + public int getStatus() { + return HttpServletResponse.SC_UNAUTHORIZED; + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/AtomNotFoundException.java b/src/main/java/com/sun/syndication/propono/atom/server/AtomNotFoundException.java new file mode 100644 index 0000000..ad71a68 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/AtomNotFoundException.java @@ -0,0 +1,48 @@ +/* + * Copyright 2007 Apache Software Foundation + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.server; + +import javax.servlet.http.HttpServletResponse; + +/** + * Exception thrown by AtomHandler in that case a resource is not found. + */ +public class AtomNotFoundException extends AtomException { + /** Construct new exception */ + public AtomNotFoundException() { + super(); + } + /** Construct new exception with message */ + public AtomNotFoundException(String msg) { + super(msg); + } + /** Construct new exception with message and root cause */ + public AtomNotFoundException(String msg, Throwable t) { + super(msg, t); + } + /** Construct new exception with root cause */ + public AtomNotFoundException(Throwable t) { + super(t); + } + /** Get HTTP status code associated with exception (404 not found) */ + public int getStatus() { + return HttpServletResponse.SC_NOT_FOUND; + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/AtomRequest.java b/src/main/java/com/sun/syndication/propono/atom/server/AtomRequest.java new file mode 100644 index 0000000..dc1e70d --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/AtomRequest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.server; + +import java.io.IOException; +import java.io.InputStream; +import java.security.Principal; +import java.util.Enumeration; +import java.util.Map; + +/** + * Represents HTTP request to be processed by AtomHandler. + */ +public interface AtomRequest { + + /** + * Returns any extra path information associated with the URL the client + * sent when it made this request. + */ + public String getPathInfo(); + + /** + * Returns the query string that is contained in the request URL after + * the path. + */ + public String getQueryString(); + + /** + * Returns the login of the user making this request, if the user has + * been authenticated, or null if the user has not been authenticated. + */ + public String getRemoteUser(); + + /** + * Returns a boolean indicating whether the authenticated user is included + * in the specified logical "role". + */ + public boolean isUserInRole(String arg0); + + /** + * Returns a java.security.Principal object containing the name of the + * current authenticated user. + */ + public Principal getUserPrincipal(); + + /** + * Returns the part of this request's URL from the protocol name up to the + * query string in the first line of the HTTP request. + */ + public String getRequestURI(); + + /** + * Reconstructs the URL the client used to make the request. + */ + public StringBuffer getRequestURL(); + + /** + * Returns the length, in bytes, of the request body and made available by + * the input stream, or -1 if the length is not known. + */ + public int getContentLength(); + + /** + * Returns the MIME type of the body of the request, or null if the type + * is not known. */ + public String getContentType(); + + /** + * Returns the value of a request parameter as a String, or null if the + * parameter does not exist. + */ + public String getParameter(String arg0); + + /** + * Returns an Enumeration of String objects containing the names of the + * parameters contained in this request. + */ + public Enumeration getParameterNames(); + + /** + * Returns an array of String objects containing all of the values the + * given request parameter has, or null if the parameter does not exist. + */ + public String[] getParameterValues(String arg0); + + /** + * Returns a java.util.Map of the parameters of this request. + */ + public Map getParameterMap(); + + /** + * Retrieves the body of the request as binary data using a + * ServletInputStream. + */ + public InputStream getInputStream() throws IOException; + + /** + * Returns the value of the specified request header as a long value that + * represents a Date object. */ + public long getDateHeader(String arg0); + + /** + * Returns the value of the specified request header as a String. + */ + public String getHeader(String arg0); + + /** + * Returns all the values of the specified request header as an Enumeration + * of String objects. + */ + public Enumeration getHeaders(String arg0); + + /** + * Returns an enumeration of all the header names this request contains. + */ + public Enumeration getHeaderNames(); + + /** + * Returns the value of the specified request header as an int. + */ + public int getIntHeader(String arg0); +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/AtomRequestImpl.java b/src/main/java/com/sun/syndication/propono/atom/server/AtomRequestImpl.java new file mode 100644 index 0000000..8be7833 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/AtomRequestImpl.java @@ -0,0 +1,114 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.server; + +import java.io.IOException; +import java.io.InputStream; +import java.security.Principal; +import java.util.Enumeration; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; + +/** + * Default request implementation. + */ +public class AtomRequestImpl implements AtomRequest { + private HttpServletRequest wrapped = null; + + public AtomRequestImpl(HttpServletRequest wrapped) { + this.wrapped = wrapped; + } + + public String getPathInfo() { + return wrapped.getPathInfo() != null ? wrapped.getPathInfo() : ""; + } + + public String getQueryString() { + return wrapped.getQueryString(); + } + + public String getRemoteUser() { + return wrapped.getRemoteUser(); + } + + public boolean isUserInRole(String arg0) { + return wrapped.isUserInRole(arg0); + } + + public Principal getUserPrincipal() { + return wrapped.getUserPrincipal(); + } + + public String getRequestURI() { + return wrapped.getRequestURI(); + } + + public StringBuffer getRequestURL() { + return wrapped.getRequestURL(); + } + + public int getContentLength() { + return wrapped.getContentLength(); + } + + public String getContentType() { + return wrapped.getContentType(); + } + + public String getParameter(String arg0) { + return wrapped.getParameter(arg0); + } + + public Enumeration getParameterNames() { + return wrapped.getParameterNames(); + } + + public String[] getParameterValues(String arg0) { + return wrapped.getParameterValues(arg0); + } + + public Map getParameterMap() { + return wrapped.getParameterMap(); + } + + public InputStream getInputStream() throws IOException { + return wrapped.getInputStream(); + } + + public long getDateHeader(String arg0) { + return wrapped.getDateHeader(arg0); + } + + public String getHeader(String arg0) { + return wrapped.getHeader(arg0); + } + + public Enumeration getHeaders(String arg0) { + return wrapped.getHeaders(arg0); + } + + public Enumeration getHeaderNames() { + return wrapped.getHeaderNames(); + } + + public int getIntHeader(String arg0) { + return wrapped.getIntHeader(arg0); + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/AtomServlet.java b/src/main/java/com/sun/syndication/propono/atom/server/AtomServlet.java new file mode 100644 index 0000000..83815ea --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/AtomServlet.java @@ -0,0 +1,378 @@ +/* + * Copyright 2007 Apache Software Foundation + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. The ASF licenses this file to You + * under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. For additional information regarding + * copyright in this work, please see the NOTICE file in the top level + * directory of this distribution. + */ +package com.sun.syndication.propono.atom.server; + +import com.sun.syndication.feed.atom.Content; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Writer; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jdom.Document; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter; +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.feed.atom.Feed; +import com.sun.syndication.feed.atom.Link; +import com.sun.syndication.io.WireFeedOutput; +import com.sun.syndication.io.impl.Atom10Generator; +import com.sun.syndication.io.impl.Atom10Parser; +import com.sun.syndication.propono.atom.common.AtomService; +import com.sun.syndication.propono.atom.common.Categories; +import com.sun.syndication.propono.utils.Utilities; +import java.io.BufferedReader; +import java.util.Collections; +import java.util.Iterator; +import javax.servlet.ServletConfig; + +/** + * Atom Servlet implements Atom protocol by calling an + * {@link com.sun.syndication.propono.atom.server.AtomHandler} + * implementation. This servlet takes care of parsing incoming XML into ROME + * Atom {@link com.sun.syndication.feed.atom.Entry} objects, passing those to the handler and serializing + * to the response the entries and feeds returned by the handler. + */ +public class AtomServlet extends HttpServlet { + + /** + * Get feed type support by Servlet, "atom_1.0" + */ + public static final String FEED_TYPE = "atom_1.0"; + private static String contextDirPath = null; + + private static Log log = + LogFactory.getFactory().getInstance(AtomServlet.class); + + static { + Atom10Parser.setResolveURIs(true); + } + + //----------------------------------------------------------------------------- + /** + * Create an Atom request handler. + * TODO: make AtomRequestHandler implementation configurable. + */ + private AtomHandler createAtomRequestHandler( + HttpServletRequest request, HttpServletResponse response) + throws ServletException { + AtomHandlerFactory ahf = AtomHandlerFactory.newInstance(); + return ahf.newAtomHandler(request, response); + } + + //----------------------------------------------------------------------------- + /** + * Handles an Atom GET by calling handler and writing results to response. + */ + protected void doGet(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException { + log.debug("Entering"); + AtomHandler handler = createAtomRequestHandler(req, res); + String userName = handler.getAuthenticatedUsername(); + if (userName != null) { + AtomRequest areq = new AtomRequestImpl(req); + try { + if (handler.isAtomServiceURI(areq)) { + // return an Atom Service document + AtomService service = handler.getAtomService(areq); + Document doc = service.serviceToDocument(); + res.setContentType("application/atomsvc+xml; charset=utf-8"); + Writer writer = res.getWriter(); + XMLOutputter outputter = new XMLOutputter(); + outputter.setFormat(Format.getPrettyFormat()); + outputter.output(doc, writer); + writer.close(); + res.setStatus(HttpServletResponse.SC_OK); + } + else if (handler.isCategoriesURI(areq)) { + Categories cats = handler.getCategories(areq); + res.setContentType("application/xml"); + Writer writer = res.getWriter(); + Document catsDoc = new Document(); + catsDoc.setRootElement(cats.categoriesToElement()); + XMLOutputter outputter = new XMLOutputter(); + outputter.output(catsDoc, writer); + writer.close(); + res.setStatus(HttpServletResponse.SC_OK); + } + else if (handler.isCollectionURI(areq)) { + // return a collection + Feed col = handler.getCollection(areq); + col.setFeedType(FEED_TYPE); + WireFeedOutput wireFeedOutput = new WireFeedOutput(); + Document feedDoc = wireFeedOutput.outputJDom(col); + res.setContentType("application/atom+xml; charset=utf-8"); + Writer writer = res.getWriter(); + XMLOutputter outputter = new XMLOutputter(); + outputter.setFormat(Format.getPrettyFormat()); + outputter.output(feedDoc, writer); + writer.close(); + res.setStatus(HttpServletResponse.SC_OK); + } + else if (handler.isEntryURI(areq)) { + // return an entry + Entry entry = handler.getEntry(areq); + if (entry != null) { + res.setContentType("application/atom+xml; type=entry; charset=utf-8"); + Writer writer = res.getWriter(); + Atom10Generator.serializeEntry(entry, writer); + writer.close(); + } else { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } + else if (handler.isMediaEditURI(areq)) { + AtomMediaResource entry = handler.getMediaResource(areq); + res.setContentType(entry.getContentType()); + res.setContentLength((int)entry.getContentLength()); + Utilities.copyInputToOutput(entry.getInputStream(), res.getOutputStream()); + res.getOutputStream().flush(); + res.getOutputStream().close(); + } + else { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } catch (AtomException ae) { + res.sendError(ae.getStatus(), ae.getMessage()); + log.debug("ERROR processing GET", ae); + } catch (Exception e) { + res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + log.debug("ERROR processing GET", e); + } + } else { + res.setHeader("WWW-Authenticate", "BASIC realm=\"AtomPub\""); + res.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } + log.debug("Exiting"); + } + + //----------------------------------------------------------------------------- + /** + * Handles an Atom POST by calling handler to identify URI, reading/parsing + * data, calling handler and writing results to response. + */ + protected void doPost(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException { + log.debug("Entering"); + AtomHandler handler = createAtomRequestHandler(req, res); + String userName = handler.getAuthenticatedUsername(); + if (userName != null) { + AtomRequest areq = new AtomRequestImpl(req); + try { + if (handler.isCollectionURI(areq)) { + + if (req.getContentType().startsWith("application/atom+xml")) { + + // parse incoming entry + Entry entry = Atom10Parser.parseEntry(new BufferedReader( + new InputStreamReader(req.getInputStream(), "UTF-8")), null); + + // call handler to post it + Entry newEntry = handler.postEntry(areq, entry); + + // set Location and Content-Location headers + for (Iterator it = newEntry.getOtherLinks().iterator(); it.hasNext();) { + Link link = (Link) it.next(); + if ("edit".equals(link.getRel())) { + res.addHeader("Location", link.getHrefResolved()); + break; + } + } + for (Iterator it = newEntry.getAlternateLinks().iterator(); it.hasNext();) { + Link link = (Link) it.next(); + if ("alternate".equals(link.getRel())) { + res.addHeader("Content-Location", link.getHrefResolved()); + break; + } + } + + // write entry back out to response + res.setStatus(HttpServletResponse.SC_CREATED); + res.setContentType("application/atom+xml; type=entry; charset=utf-8"); + + Writer writer = res.getWriter(); + Atom10Generator.serializeEntry(newEntry, writer); + writer.close(); + + } else if (req.getContentType() != null) { + + // get incoming title and slug from HTTP header + String title = areq.getHeader("Title"); + + // create new entry for resource, set title and type + Entry resource = new Entry(); + resource.setTitle(title); + Content content = new Content(); + content.setType(areq.getContentType()); + resource.setContents(Collections.singletonList(content)); + + // hand input stream off to hander to post file + Entry newEntry = handler.postMedia(areq, resource); + + // set Location and Content-Location headers + for (Iterator it = newEntry.getOtherLinks().iterator(); it.hasNext();) { + Link link = (Link) it.next(); + if ("edit".equals(link.getRel())) { + res.addHeader("Location", link.getHrefResolved()); + break; + } + } + for (Iterator it = newEntry.getAlternateLinks().iterator(); it.hasNext();) { + Link link = (Link) it.next(); + if ("alternate".equals(link.getRel())) { + res.addHeader("Content-Location", link.getHrefResolved()); + break; + } + } + + res.setStatus(HttpServletResponse.SC_CREATED); + res.setContentType("application/atom+xml; type=entry; charset=utf-8"); + + Writer writer = res.getWriter(); + Atom10Generator.serializeEntry(newEntry, writer); + writer.close(); + + } else { + res.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE, + "No content-type specified in request"); + } + + } else { + res.sendError(HttpServletResponse.SC_NOT_FOUND, + "Invalid collection specified in request"); + } + } catch (AtomException ae) { + res.sendError(ae.getStatus(), ae.getMessage()); + log.debug("ERROR processing POST", ae); + } catch (Exception e) { + res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + log.debug("ERROR processing POST", e); + } + } else { + res.setHeader("WWW-Authenticate", "BASIC realm=\"AtomPub\""); + res.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } + log.debug("Exiting"); + } + + //----------------------------------------------------------------------------- + /** + * Handles an Atom PUT by calling handler to identify URI, reading/parsing + * data, calling handler and writing results to response. + */ + protected void doPut(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException { + log.debug("Entering"); + AtomHandler handler = createAtomRequestHandler(req, res); + String userName = handler.getAuthenticatedUsername(); + if (userName != null) { + AtomRequest areq = new AtomRequestImpl(req); + try { + if (handler.isEntryURI(areq)) { + + // parse incoming entry + Entry unsavedEntry = Atom10Parser.parseEntry(new BufferedReader( + new InputStreamReader(req.getInputStream(), "UTF-8")), null); + + // call handler to put entry + handler.putEntry(areq, unsavedEntry); + + res.setStatus(HttpServletResponse.SC_OK); + + } else if (handler.isMediaEditURI(areq)) { + + // hand input stream to handler + handler.putMedia(areq); + + res.setStatus(HttpServletResponse.SC_OK); + + } else { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } catch (AtomException ae) { + res.sendError(ae.getStatus(), ae.getMessage()); + log.debug("ERROR processing PUT", ae); + } catch (Exception e) { + res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + log.debug("ERROR processing PUT", e); + } + } else { + res.setHeader("WWW-Authenticate", "BASIC realm=\"AtomPub\""); + // Wanted to use sendError() here but Tomcat sends 403 forbidden + // when I do that, so sticking with setStatus() for time being. + res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + log.debug("Exiting"); + } + + //----------------------------------------------------------------------------- + /** + * Handle Atom DELETE by calling appropriate handler. + */ + protected void doDelete(HttpServletRequest req, HttpServletResponse res) + throws ServletException, IOException { + log.debug("Entering"); + AtomHandler handler = createAtomRequestHandler(req, res); + String userName = handler.getAuthenticatedUsername(); + if (userName != null) { + AtomRequest areq = new AtomRequestImpl(req); + try { + if (handler.isEntryURI(areq)) { + handler.deleteEntry(areq); + res.setStatus(HttpServletResponse.SC_OK); + } + else { + res.setStatus(HttpServletResponse.SC_NOT_FOUND); + } + } catch (AtomException ae) { + res.sendError(ae.getStatus(), ae.getMessage()); + log.debug("ERROR processing DELETE", ae); + } catch (Exception e) { + res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + log.debug("ERROR processing DELETE", e); + } + } else { + res.setHeader("WWW-Authenticate", "BASIC realm=\"AtomPub\""); + // Wanted to use sendError() here but Tomcat sends 403 forbidden + // when I do that, so sticking with setStatus() for time being. + res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + } + log.debug("Exiting"); + } + + /** + * Initialize servlet. + */ + public void init( ServletConfig config ) throws ServletException { + super.init( config ); + contextDirPath = getServletContext().getRealPath("/"); + + } + + /** + * Get absolute path to Servlet context directory. + */ + public static String getContextDirPath() { + return contextDirPath; + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/FactoryConfigurationError.java b/src/main/java/com/sun/syndication/propono/atom/server/FactoryConfigurationError.java new file mode 100644 index 0000000..b35e0ff --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/FactoryConfigurationError.java @@ -0,0 +1,106 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.server; + +/** + * Thrown when a problem with configuration with the + * {@link com.sun.syndication.propono.atom.server.AtomHandlerFactory} exists. + * This error will typically be thrown when the class of a parser factory + * specified in the system properties cannot be found or instantiated. + */ +public class FactoryConfigurationError extends Error { + + /** + * Exception that represents the error. + */ + private Exception exception; + + /** + * Create a new FactoryConfigurationError with no + * detail mesage. + */ + public FactoryConfigurationError() { + super(); + this.exception = null; + } + + /** + * Create a new FactoryConfigurationError with + * the String specified as an error message. + * + * @param msg The error message for the exception. + */ + public FactoryConfigurationError(String msg) { + super(msg); + this.exception = null; + } + + + /** + * Create a new FactoryConfigurationError with a + * given Exception base cause of the error. + * + * @param e The exception to be encapsulated in a + * FactoryConfigurationError. + */ + public FactoryConfigurationError(Exception e) { + super(e.toString()); + this.exception = e; + } + + /** + * Create a new FactoryConfigurationError with the + * given Exception base cause and detail message. + * + * @param e The exception to be encapsulated in a + * FactoryConfigurationError + * @param msg The detail message. + */ + public FactoryConfigurationError(Exception e, String msg) { + super(msg); + this.exception = e; + } + + + /** + * Return the message (if any) for this error . If there is no + * message for the exception and there is an encapsulated + * exception then the message of that exception, if it exists will be + * returned. Else the name of the encapsulated exception will be + * returned. + * + * @return The error message. + */ + public String getMessage() { + String message = super.getMessage(); + + if (message == null && exception != null) { + return exception.getMessage(); + } + + return message; + } + + /** + * Return the actual exception (if any) that caused this exception to + * be raised. + * + * @return The encapsulated exception, or null if there is none. + */ + public Exception getException() { + return exception; + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/FactoryFinder.java b/src/main/java/com/sun/syndication/propono/atom/server/FactoryFinder.java new file mode 100644 index 0000000..67fbc6e --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/FactoryFinder.java @@ -0,0 +1,280 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.server; + +import java.io.File; +import java.io.FileInputStream; + +import java.util.Properties; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; + +/** + * Find {@link com.sun.syndication.propono.atom.server.AtomHandlerFactory} based on properties files. + */ +class FactoryFinder { + private static boolean debug = false; + static Properties cacheProps= new Properties(); + static SecuritySupport ss = new SecuritySupport() ; + static boolean firstTime = true; + + private static void dPrint(String msg) { + if (debug) { + System.err.println("Propono: " + msg); + } + } + + /** + * Create an instance of a class using the specified ClassLoader and + * optionally fall back to the current ClassLoader if not found. + * + * @param className Name of the concrete class corresponding to the + * service provider + * + * @param cl ClassLoader to use to load the class, null means to use + * the bootstrap ClassLoader + * + * @param doFallback true if the current ClassLoader should be tried as + * a fallback if the class is not found using cl + */ + private static Object newInstance( + String className, ClassLoader cl, boolean doFallback) + throws ConfigurationError { + + try { + Class providerClass; + if (cl == null) { + // If classloader is null Use the bootstrap ClassLoader. + // Thus Class.forName(String) will use the current + // ClassLoader which will be the bootstrap ClassLoader. + providerClass = Class.forName(className); + } else { + try { + providerClass = cl.loadClass(className); + } catch (ClassNotFoundException x) { + if (doFallback) { + // Fall back to current classloader + cl = FactoryFinder.class.getClassLoader(); + providerClass = cl.loadClass(className); + } else { + throw x; + } + } + } + + Object instance = providerClass.newInstance(); + dPrint("created new instance of " + providerClass + + " using ClassLoader: " + cl); + return instance; + } catch (ClassNotFoundException x) { + throw new ConfigurationError( + "Provider " + className + " not found", x); + } catch (Exception x) { + throw new ConfigurationError( + "Provider " + className + " could not be instantiated: " + x, x); + } + } + + /** + * Finds the implementation Class object in the specified order. Main + * entry point. + * @return Class object of factory, never null + * + * @param factoryId Name of the factory to find, same as + * a property name + * @param fallbackClassName Implementation class name, if nothing else + * is found. Use null to mean no fallback. + * + * Package private so this code can be shared. + */ + static Object find(String factoryId, String fallbackClassName) + throws ConfigurationError { + + // Figure out which ClassLoader to use for loading the provider + // class. If there is a Context ClassLoader then use it. + + ClassLoader classLoader = ss.getContextClassLoader(); + + if (classLoader == null) { + // if we have no Context ClassLoader + // so use the current ClassLoader + classLoader = FactoryFinder.class.getClassLoader(); + } + + dPrint("find factoryId =" + factoryId); + + // Use the system property first + try { + String systemProp = ss.getSystemProperty(factoryId); + if( systemProp!=null) { + dPrint("found system property, value=" + systemProp); + return newInstance(systemProp, classLoader, true ); + } + } catch (SecurityException se) { + //if first option fails due to any reason we should try next option in the + //look up algorithm. + } + + // try to read from /propono.properties + try { + String javah = ss.getSystemProperty("java.home"); + String configFile = "/propono.properties"; + String factoryClassName = null; + if(firstTime){ + synchronized(cacheProps){ + if (firstTime) { + try { + InputStream is = FactoryFinder.class.getResourceAsStream(configFile); + firstTime = false; + if (is != null) { + dPrint("Read properties file: " + configFile); + cacheProps.load(is); + } + } catch (Exception intentionallyIgnored) {} + } + } + } + factoryClassName = cacheProps.getProperty(factoryId); + + if(factoryClassName != null){ + dPrint("found in $java.home/propono.properties, value=" + factoryClassName); + return newInstance(factoryClassName, classLoader, true); + } + } catch(Exception ex) { + if( debug ) ex.printStackTrace(); + } + + // Try Jar Service Provider Mechanism + Object provider = findJarServiceProvider(factoryId); + if (provider != null) { + return provider; + } + if (fallbackClassName == null) { + throw new ConfigurationError( + "Provider for " + factoryId + " cannot be found", null); + } + + dPrint("loaded from fallback value: " + fallbackClassName); + return newInstance(fallbackClassName, classLoader, true); + } + + /* + * Try to find provider using Jar Service Provider Mechanism + * + * @return instance of provider class if found or null + */ + private static Object findJarServiceProvider(String factoryId) + throws ConfigurationError { + + String serviceId = "META-INF/services/" + factoryId; + InputStream is = null; + + // First try the Context ClassLoader + ClassLoader cl = ss.getContextClassLoader(); + if (cl != null) { + is = ss.getResourceAsStream(cl, serviceId); + + // If no provider found then try the current ClassLoader + if (is == null) { + cl = FactoryFinder.class.getClassLoader(); + is = ss.getResourceAsStream(cl, serviceId); + } + } else { + // No Context ClassLoader, try the current + // ClassLoader + cl = FactoryFinder.class.getClassLoader(); + is = ss.getResourceAsStream(cl, serviceId); + } + + if (is == null) { + // No provider found + return null; + } + + dPrint("found jar resource=" + serviceId + + " using ClassLoader: " + cl); + + // Read the service provider name in UTF-8 as specified in + // the jar spec. Unfortunately this fails in Microsoft + // VJ++, which does not implement the UTF-8 + // encoding. Theoretically, we should simply let it fail in + // that case, since the JVM is obviously broken if it + // doesn't support such a basic standard. But since there + // are still some users attempting to use VJ++ for + // development, we have dropped in a fallback which makes a + // second attempt using the platform's default encoding. In + // VJ++ this is apparently ASCII, which is a subset of + // UTF-8... and since the strings we'll be reading here are + // also primarily limited to the 7-bit ASCII range (at + // least, in English versions), this should work well + // enough to keep us on the air until we're ready to + // officially decommit from VJ++. [Edited comment from + // jkesselm] + BufferedReader rd; + try { + rd = new BufferedReader(new InputStreamReader(is, "UTF-8")); + } catch (java.io.UnsupportedEncodingException e) { + rd = new BufferedReader(new InputStreamReader(is)); + } + + String factoryClassName = null; + try { + // XXX Does not handle all possible input as specified by the + // Jar Service Provider specification + factoryClassName = rd.readLine(); + rd.close(); + } catch (IOException x) { + // No provider found + return null; + } + + if (factoryClassName != null && + ! "".equals(factoryClassName)) { + dPrint("found in resource, value=" + + factoryClassName); + + // Note: here we do not want to fall back to the current + // ClassLoader because we want to avoid the case where the + // resource file was found using one ClassLoader and the + // provider class was instantiated using a different one. + return newInstance(factoryClassName, cl, false); + } + + // No provider found + return null; + } + + static class ConfigurationError extends Error { + private Exception exception; + + /** + * Construct a new instance with the specified detail string and + * exception. + */ + ConfigurationError(String msg, Exception x) { + super(msg); + this.exception = x; + } + + Exception getException() { + return exception; + } + } + +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/SecuritySupport.java b/src/main/java/com/sun/syndication/propono/atom/server/SecuritySupport.java new file mode 100644 index 0000000..ab64b57 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/SecuritySupport.java @@ -0,0 +1,90 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.server; + +import java.security.*; +import java.net.*; +import java.io.*; +import java.util.*; + +/** + * This class is duplicated for each subpackage, it is package private and + * therefore is not exposed as part of the public API. + */ +class SecuritySupport { + + ClassLoader getContextClassLoader() { + return (ClassLoader) + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + ClassLoader cl = null; + try { + cl = Thread.currentThread().getContextClassLoader(); + } catch (SecurityException ex) { } + return cl; + } + }); + } + + String getSystemProperty(final String propName) { + return (String) + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return System.getProperty(propName); + } + }); + } + + FileInputStream getFileInputStream(final File file) + throws FileNotFoundException { + try { + return (FileInputStream) + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws FileNotFoundException { + return new FileInputStream(file); + } + }); + } catch (PrivilegedActionException e) { + throw (FileNotFoundException)e.getException(); + } + } + + InputStream getResourceAsStream(final ClassLoader cl, + final String name) { + return (InputStream) + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + InputStream ris; + if (cl == null) { + ris = ClassLoader.getSystemResourceAsStream(name); + } else { + ris = cl.getResourceAsStream(name); + } + return ris; + } + }); + } + + boolean doesFileExist(final File f) { + return ((Boolean) + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + return new Boolean(f.exists()); + } + })).booleanValue(); + } + +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomHandler.java b/src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomHandler.java new file mode 100644 index 0000000..018b8b2 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomHandler.java @@ -0,0 +1,443 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.server.impl; + +import com.sun.syndication.propono.atom.server.AtomMediaResource; +import org.apache.commons.codec.binary.Base64; +import com.sun.syndication.propono.atom.server.AtomHandler; +import com.sun.syndication.propono.atom.server.AtomException; +import com.sun.syndication.propono.atom.server.AtomServlet; +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.feed.atom.Feed; +import com.sun.syndication.propono.atom.common.AtomService; +import com.sun.syndication.propono.atom.common.Categories; +import com.sun.syndication.propono.atom.server.AtomRequest; +import java.io.File; +import java.util.StringTokenizer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import javax.servlet.http.HttpServletRequest; +import org.apache.commons.lang.StringUtils; + + +/** + * File-based {@link com.sun.syndication.propono.atom.server.AtomHandler} + * implementation that stores entries and media-entries to disk. Implemented + * using {@link com.sun.syndication.propono.atom.server.impl.FileBasedAtomService}. + */ +public class FileBasedAtomHandler implements AtomHandler { + + private static Log log = + LogFactory.getFactory().getInstance(FileBasedAtomHandler.class); + + private static String fileStoreDir = null; + + private String userName = null; + private String atomProtocolURL = null; + private String contextURI = null; + private String uploadurl = null; + + private FileBasedAtomService service = null; + + /** + * Construct handler to handle one request. + * @param req Request to be handled. + */ + public FileBasedAtomHandler( HttpServletRequest req ) { + this(req, AtomServlet.getContextDirPath()); + } + + /** + * Contruct handler for one request, using specified file storage directory. + * @param req Request to be handled. + * @param uploaddir File storage upload dir. + */ + public FileBasedAtomHandler(HttpServletRequest req, String uploaddir) { + log.debug("ctor"); + + userName = authenticateBASIC(req); + + atomProtocolURL = req.getScheme() + "://" + req.getServerName() + ":" + + req.getServerPort() + req.getContextPath() + req.getServletPath(); + + contextURI = req.getScheme() + "://" + req.getServerName() + ":" + + req.getServerPort() + req.getContextPath(); + + try { + service = new FileBasedAtomService(userName, uploaddir, + contextURI, req.getContextPath(), req.getServletPath()); + } catch (Throwable t) { + throw new RuntimeException("ERROR creating FileBasedAtomService", t); + } + } + + /** + * Method used for validating user. Developers can overwrite this method + * and use credentials stored in Database or LDAP to confirm if the user is + * allowed to access this service. + * @param login user submitted login id + * @param password user submitted password + */ + public boolean validateUser(String login, String password) { + return true; + } + + /** + * Get username of authenticated user + * @return User name. + */ + public String getAuthenticatedUsername() { + // For now return userName as the login id entered for authorization + return userName; + } + + /** + * Get base URI of Atom protocol implementation. + * @return Base URI of Atom protocol implemenation. + */ + public String getAtomProtocolURL( ) { + if ( atomProtocolURL == null ) { + return "app"; + } else { + return atomProtocolURL; + } + } + + /** + * Return introspection document + * @throws com.sun.syndication.propono.atom.server.AtomException Unexpected exception. + * @return AtomService object with workspaces and collections. + */ + public AtomService getAtomService(AtomRequest areq) throws AtomException { + return service; + } + + /** + * Returns null because we use in-line categories. + * @throws com.sun.syndication.propono.atom.server.AtomException Unexpected exception. + * @return Categories object + */ + public Categories getCategories(AtomRequest areq) throws AtomException { + log.debug("getCollection"); + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + String handle = pathInfo[0]; + String collection = pathInfo[1]; + FileBasedCollection col = service.findCollectionByHandle(handle, collection); + return (Categories)col.getCategories(true).get(0); + } + + /** + * Get collection specified by pathinfo. + * @param areq Details of HTTP request + * @return ROME feed representing collection. + * @throws com.sun.syndication.propono.atom.server.AtomException Invalid collection or other exception. + */ + public Feed getCollection(AtomRequest areq) throws AtomException { + log.debug("getCollection"); + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + String handle = pathInfo[0]; + String collection = pathInfo[1]; + FileBasedCollection col = service.findCollectionByHandle(handle, collection); + return col.getFeedDocument(); + } + + /** + * Create a new entry specified by pathInfo and posted entry. We save the + * submitted Atom entry verbatim, but we do set the id and reset the update + * time. + * + * @param entry Entry to be added to collection. + * @param areq Details of HTTP request + * @throws com.sun.syndication.propono.atom.server.AtomException On invalid collection or other error. + * @return Entry as represented on server. + */ + public Entry postEntry(AtomRequest areq, Entry entry) throws AtomException { + log.debug("postEntry"); + + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + String handle = pathInfo[0]; + String collection = pathInfo[1]; + FileBasedCollection col = service.findCollectionByHandle(handle, collection); + try { + return col.addEntry(entry); + + } catch (Exception fe) { + fe.printStackTrace(); + throw new AtomException( fe ); + } + } + + /** + * Get entry specified by pathInfo. + * @param areq Details of HTTP request + * @throws com.sun.syndication.propono.atom.server.AtomException On invalid pathinfo or other error. + * @return ROME Entry object. + */ + public Entry getEntry(AtomRequest areq) throws AtomException { + log.debug("getEntry"); + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + String handle = pathInfo[0]; + String collection = pathInfo[1]; + String fileName = pathInfo[2]; + FileBasedCollection col = service.findCollectionByHandle(handle, collection); + try { + return col.getEntry(fileName); + + } catch (Exception re) { + if (re instanceof AtomException) throw (AtomException)re; + throw new AtomException("ERROR: getting entry", re); + } + } + + /** + * Update entry specified by pathInfo and posted entry. + * + * @param entry + * @param areq Details of HTTP request + * @throws com.sun.syndication.propono.atom.server.AtomException + */ + public void putEntry(AtomRequest areq, Entry entry) throws AtomException { + log.debug("putEntry"); + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + String handle = pathInfo[0]; + String collection = pathInfo[1]; + String fileName = pathInfo[2]; + FileBasedCollection col = service.findCollectionByHandle(handle, collection); + try { + col.updateEntry(entry, fileName); + + } catch ( Exception fe ) { + throw new AtomException( fe ); + } + } + + + /** + * Delete entry specified by pathInfo. + * @param areq Details of HTTP request + */ + public void deleteEntry(AtomRequest areq) throws AtomException { + log.debug("deleteEntry"); + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + String handle = pathInfo[0]; + String collection = pathInfo[1]; + String fileName = pathInfo[2]; + FileBasedCollection col = service.findCollectionByHandle(handle, collection); + try { + col.deleteEntry(fileName); + + } catch (Exception e) { + String msg = "ERROR in atom.deleteResource"; + log.error(msg,e); + throw new AtomException(msg); + } + } + + + /** + * Store media data in collection specified by pathInfo, create an Atom + * media-link entry to store metadata for the new media file and return + * that entry to the caller. + * @param areq Details of HTTP request + * @param entry New entry initialzied with only title and content type + * @return Location URL of new media entry + */ + public Entry postMedia(AtomRequest areq, Entry entry) throws AtomException { + + // get incoming slug from HTTP header + String slug = areq.getHeader("Slug"); + + if (log.isDebugEnabled()) { + log.debug("postMedia - title: "+entry.getTitle()+" slug:"+slug); + } + + try { + File tempFile = null; + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + String handle = pathInfo[0]; + String collection = pathInfo[1]; + FileBasedCollection col = service.findCollectionByHandle(handle, collection); + try { + col.addMediaEntry(entry, slug, areq.getInputStream()); + + } catch (Exception e) { + e.printStackTrace(); + String msg = "ERROR reading posted file"; + log.error(msg,e); + throw new AtomException(msg, e); + } finally { + if (tempFile != null) tempFile.delete(); + } + + } catch (Exception re) { + throw new AtomException("ERROR: posting media"); + } + return entry; + } + + /** + * Update the media file part of a media-link entry. + * @param areq Details of HTTP request + * Assuming pathInfo of form /user-name/resource/name + */ + public void putMedia(AtomRequest areq) throws AtomException { + + log.debug("putMedia"); + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + String handle = pathInfo[0]; + String collection = pathInfo[1]; + String fileName = pathInfo[3]; + FileBasedCollection col = service.findCollectionByHandle(handle, collection); + try { + col.updateMediaEntry(fileName, areq.getContentType(), areq.getInputStream()); + + } catch (Exception re) { + throw new AtomException("ERROR: posting media"); + } + } + + public AtomMediaResource getMediaResource(AtomRequest areq) throws AtomException { + log.debug("putMedia"); + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + String handle = pathInfo[0]; + String collection = pathInfo[1]; + String fileName = pathInfo[3]; + FileBasedCollection col = service.findCollectionByHandle(handle, collection); + try { + return col.getMediaResource(fileName); + + } catch (Exception re) { + throw new AtomException("ERROR: posting media"); + } + } + + /** + * Return true if specified pathinfo represents URI of service doc. + */ + public boolean isAtomServiceURI(AtomRequest areq) { + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + if (pathInfo.length==0) return true; + return false; + } + + /** + * Return true if specified pathinfo represents URI of category doc. + */ + public boolean isCategoriesURI(AtomRequest areq) { + log.debug("isCategoriesDocumentURI"); + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + if (pathInfo.length == 3 && "categories".equals(pathInfo[2])) { + return true; + } + return false; + } + + /** + * Return true if specified pathinfo represents URI of a collection. + */ + public boolean isCollectionURI(AtomRequest areq) { + log.debug("isCollectionURI"); + // workspace/collection-plural + // if length is 2 and points to a valid collection then YES + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + if (pathInfo.length == 2) { + String handle = pathInfo[0]; + String collection = pathInfo[1]; + if (service.findCollectionByHandle(handle, collection) != null) { + return true; + } + } + return false; + + } + + /** + * Return true if specified pathinfo represents URI of an Atom entry. + */ + public boolean isEntryURI(AtomRequest areq) { + log.debug("isEntryURI"); + // workspace/collection-singular/fsid + // if length is 3 and points to a valid collection then YES + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + if (pathInfo.length == 3) { + String handle = pathInfo[0]; + String collection = pathInfo[1]; + if (service.findCollectionByHandle(handle, collection) != null) { + return true; + } + } + return false; + } + + /** + * Return true if specified pathinfo represents media-edit URI. + */ + public boolean isMediaEditURI(AtomRequest areq) { + log.debug("isMediaEditURI"); + // workspace/collection-singular/fsid/media/fsid + // if length is 4, points to a valid collection and fsid is mentioned twice then YES + String[] pathInfo = StringUtils.split(areq.getPathInfo(),"/"); + if (pathInfo.length == 4) { + String handle = pathInfo[0]; + String collection = pathInfo[1]; + String media = pathInfo[2]; + String fsid = pathInfo[3]; + if (service.findCollectionByHandle(handle, collection) != null && media.equals("media")) { + return true; + } + } + return false; + + } + + /** + * BASIC authentication. + */ + public String authenticateBASIC(HttpServletRequest request) { + log.debug("authenticateBASIC"); + boolean valid = false; + String userID = null; + String password = null; + try { + String authHeader = request.getHeader("Authorization"); + if (authHeader != null) { + StringTokenizer st = new StringTokenizer(authHeader); + if (st.hasMoreTokens()) { + String basic = st.nextToken(); + if (basic.equalsIgnoreCase("Basic")) { + String credentials = st.nextToken(); + String userPass = new String(Base64.decodeBase64(credentials.getBytes())); + int p = userPass.indexOf(":"); + if (p != -1) { + userID = userPass.substring(0, p); + password = userPass.substring(p+1); + + // Validate the User. + valid = validateUser( userID, password ); + } + } + } + } + } catch (Exception e) { + log.debug(e); + } + if (valid) { + //For now assume userID as userName + return userID; + } + return null; + } +} diff --git a/src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomHandlerFactory.java b/src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomHandlerFactory.java new file mode 100644 index 0000000..74ba434 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomHandlerFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.server.impl; + +import com.sun.syndication.propono.atom.server.AtomHandlerFactory; +import com.sun.syndication.propono.atom.server.AtomHandler; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Extends {@link com.sun.syndication.propono.atom.server.AtomHandlerFactory} to create and return + * {@link com.sun.syndication.propono.atom.server.impl.FileBasedAtomHandler}. + */ +public class FileBasedAtomHandlerFactory extends AtomHandlerFactory { + + /** + * Create new AtomHandler. + */ + public AtomHandler newAtomHandler( + HttpServletRequest req, HttpServletResponse res ) { + return new FileBasedAtomHandler(req); + } +} + diff --git a/src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomService.java b/src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomService.java new file mode 100644 index 0000000..9615a74 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/atom/server/impl/FileBasedAtomService.java @@ -0,0 +1,188 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.server.impl; + +import com.sun.syndication.propono.atom.common.AtomService; +import com.sun.syndication.propono.utils.Utilities; +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; +import java.util.TreeMap; + +/** + * File based Atom service. + * Supports one workspace per user. + * Collections in workspace are defined in /propono.properties, for example: + * + *
+ *    # Define list of collections to be offered
+ *    propono.atomserver.filebased.collections=entries,gifimages
+ *
+ *    # Defines 'entries' collection, accepts entries
+ *    propono.atomserver.filebased.collection.entries.title=Entries
+ *    propono.atomserver.filebased.collection.entries.singular=entry
+ *    propono.atomserver.filebased.collection.entries.plural=entries
+ *    propono.atomserver.filebased.collection.entries.accept=application/atom+xml;type=entry
+ *    propono.atomserver.filebased.collection.entries.categories=general,category1,category2
+ *
+ *    # Defines 'gifimages' collection, accepts only GIF files
+ *    propono.atomserver.filebased.collection.gifimages.title=GIF Images
+ *    propono.atomserver.filebased.collection.gifimages.singular=gif
+ *    propono.atomserver.filebased.collection.gifimages.plural=gifs
+ *    propono.atomserver.filebased.collection.gifimages.accept=image/gif
+ *    propono.atomserver.filebased.collection.gifimages.categories=general,category1,category2
+ * 
+ * + * If no such properties are found, then service will fall back to two + * collections: 'entries' for Atom entries and 'resources' for any content-type. + * + * + *

URI structure used for accessing collections and entries

+ * + *

Collection feed (URI allows GET to get collection, POST to add to it)
+ * [servlet-context-uri]/app/[workspace-handle]/[collection-plural] + *

+ * + *

Collection entry (URI allows GET, PUT and DELETE)
+ * [servlet-context-uri]/app/[workspace-handle]/[collection-singular]/[entryid] + *

+ * + *

Collection entry media (URI allows GET, PUT and DELETE)
+ * [servlet-context-uri]/app/[workspace-handle]/[collection-singular]/media/[entryid] + *

+ * + *

Categories URI if not using inline categories (URI allows GET)
+ * [servlet-context-uri]/app/[workspace-handle]/[collection-plural]/categories + *

+ * + * + *

Directory structure used to store collections and entries

+ * + *

Collection feed (kept constantly up to date)
+ * [servlet-context-dir]/[workspace-handle]/[collection-plural]/feed.xml + *

+ * + *

Collection entry (individual entries also stored as entry.xml files)
+ * [servlet-context-dir]/[workspace-handle]/[collection-plural]/id/entry.xml + *

+ * + *

Collection entry media (media file stored under entry directory)
+ * [servlet-context-dir]/[workspace-handle]/[collection-plural]/id/media/id + *

+ */ +public class FileBasedAtomService extends AtomService { + private Map workspaceMap = new TreeMap(); + private Map collectionMap = new TreeMap(); + private static Properties cacheProps = new Properties(); + private boolean firstTime = true; + + /** + * Creates a new instance of FileBasedAtomService. + */ + public FileBasedAtomService( + String userName, String baseDir, String contextURI, String contextPath, String servletPath) throws Exception { + String workspaceHandle = userName; + + // One workspace per user + FileBasedWorkspace workspace = new FileBasedWorkspace(workspaceHandle, baseDir); + workspaceMap.put(userName, workspace); + + if (firstTime) { + synchronized(cacheProps) { + InputStream is = getClass().getResourceAsStream("/propono.properties"); + if (is != null) cacheProps.load(is); + firstTime = false; + } + } + // can't find propono.properties, so use system props instead + if (cacheProps == null) cacheProps = System.getProperties(); + + String relativeURIsString = cacheProps.getProperty( + "propono.atomserver.filebased.relativeURIs"); + boolean relativeURIs = "true".equals(relativeURIsString); + + String inlineCategoriesString = cacheProps.getProperty( + "propono.atomserver.filebased.inlineCategories"); + boolean inlineCategories = "true".equals(inlineCategoriesString); + + String colnames = cacheProps.getProperty("propono.atomserver.filebased.collections"); + if (colnames != null) { + + // collections specified in propono.properties, use those + + String[] colarray = Utilities.stringToStringArray(colnames,","); + for (int i=0; i[collection-plural]/[entryid]/media/[entryid]. + * An Atom entry will be created to store metadata for the entry and it will exist + * at the path [collection-plural]/[entryid]/entry.xml. + * The entry will be added to the collection's feed in [collection-plural]/feed.xml. + * @param entry Entry object + * @param slug String to be used in file-name + * @param is Source of media data + * @throws java.lang.Exception On Error + * @return Location URI of entry + */ + public String addMediaEntry(Entry entry, String slug, InputStream is) throws Exception { + synchronized (FileStore.getFileStore()) { + + // Save media file temp file + Content content = (Content)entry.getContents().get(0); + if (entry.getTitle() == null) { + entry.setTitle(slug); + } + String fileName = createFileName((slug != null) ? slug : entry.getTitle(), content.getType()); + File tempFile = File.createTempFile(fileName, "tmp"); + FileOutputStream fos = new FileOutputStream(tempFile); + Utilities.copyInputToOutput(is, fos); + fos.close(); + + // Save media file + FileInputStream fis = new FileInputStream(tempFile); + saveMediaFile(fileName, content.getType(), tempFile.length(), fis); + fis.close(); + File resourceFile = new File(getEntryMediaPath(fileName)); + + // Create media-link entry + updateTimestamps(entry); + + // Save media-link entry + String entryPath = getEntryPath(fileName); + OutputStream os = FileStore.getFileStore().getFileOutputStream(entryPath); + updateMediaEntryAppLinks(entry, resourceFile.getName(), true); + Atom10Generator.serializeEntry(entry, new OutputStreamWriter(os, "UTF-8")); + os.flush(); + os.close(); + + // Update feed with new entry + Feed f = getFeedDocument(); + updateMediaEntryAppLinks(entry, resourceFile.getName(), false); + updateFeedDocumentWithNewEntry(f, entry); + + return getEntryEditURI(fileName, false, true); + } + } + + /** + * Get an entry from the collection. + * @param fsid Internal ID of entry to be returned + * @throws java.lang.Exception On error + * @return Entry specified by fileName/ID + */ + public Entry getEntry(String fsid) throws Exception { + if (fsid.endsWith(".media-link")) { + fsid = fsid.substring(0, fsid.length() - ".media-link".length()); + } + + String entryPath = getEntryPath(fsid); + + checkExistence(entryPath); + InputStream in = FileStore.getFileStore().getFileInputStream(entryPath); + + final Entry entry; + String filePath = getEntryMediaPath(fsid); + File resource = new File(fsid); + if (resource.exists()) { + entry = loadAtomResourceEntry(in, resource); + updateMediaEntryAppLinks(entry, fsid, true); + } else { + entry = loadAtomEntry(in); + updateEntryAppLinks(entry, fsid, true); + } + return entry; + } + + /** + * Get media resource wrapping a file. + */ + public AtomMediaResource getMediaResource(String fileName) throws Exception { + String filePath = getEntryMediaPath(fileName); + File resource = new File(filePath); + return new AtomMediaResource(resource); + } + + /** + * Update an entry in the collection. + * @param entry Updated entry to be stored + * @param fsid Internal ID of entry + * @throws java.lang.Exception On error + */ + public void updateEntry(Entry entry, String fsid) throws Exception { + synchronized (FileStore.getFileStore()) { + + Feed f = getFeedDocument(); + + if (fsid.endsWith(".media-link")) { + fsid = fsid.substring(0, fsid.length() - ".media-link".length()); + } + + updateTimestamps(entry); + + updateEntryAppLinks(entry, fsid, false); + updateFeedDocumentWithExistingEntry(f, entry); + + String entryPath = getEntryPath(fsid); + OutputStream os = FileStore.getFileStore().getFileOutputStream(entryPath); + updateEntryAppLinks(entry, fsid, true); + Atom10Generator.serializeEntry(entry, new OutputStreamWriter(os, "UTF-8")); + os.flush(); + os.close(); + } + } + + /** + * Update media associated with a media-link entry. + * @param fileName Internal ID of entry being updated + * @param contentType Content type of data + * @param is Source of updated data + * @throws java.lang.Exception On error + * @return Updated Entry as it exists on server + */ + public Entry updateMediaEntry(String fileName, String contentType, InputStream is) throws Exception { + synchronized (FileStore.getFileStore()) { + + File tempFile = File.createTempFile(fileName, "tmp"); + FileOutputStream fos = new FileOutputStream(tempFile); + Utilities.copyInputToOutput(is, fos); + fos.close(); + + // Update media file + FileInputStream fis = new FileInputStream(tempFile); + saveMediaFile(fileName, contentType, tempFile.length(), fis); + fis.close(); + File resourceFile = new File(getEntryMediaPath(fileName)); + + // Load media-link entry to return + String entryPath = getEntryPath(fileName); + InputStream in = FileStore.getFileStore().getFileInputStream(entryPath); + Entry atomEntry = loadAtomResourceEntry(in, resourceFile); + + updateTimestamps(atomEntry); + updateMediaEntryAppLinks(atomEntry, fileName, false); + + // Update feed with new entry + Feed f = getFeedDocument(); + updateFeedDocumentWithExistingEntry(f, atomEntry); + + // Save updated media-link entry + OutputStream os = FileStore.getFileStore().getFileOutputStream(entryPath); + updateMediaEntryAppLinks(atomEntry, fileName, true); + Atom10Generator.serializeEntry(atomEntry, new OutputStreamWriter(os, "UTF-8")); + os.flush(); + os.close(); + + return atomEntry; + } + } + + /** + * Delete an entry and any associated media file. + * @param fsid Internal ID of entry + * @throws java.lang.Exception On error + */ + public void deleteEntry(String fsid) throws Exception { + synchronized (FileStore.getFileStore()) { + + // Remove entry from Feed + Feed feed = getFeedDocument(); + updateFeedDocumentRemovingEntry(feed, fsid); + + String entryFilePath = this.getEntryPath(fsid); + FileStore.getFileStore().deleteFile(entryFilePath); + + String entryMediaPath = this.getEntryMediaPath(fsid); + if (entryMediaPath != null) { + FileStore.getFileStore().deleteFile(entryMediaPath); + } + + String entryDirPath = getEntryDirPath(fsid); + FileStore.getFileStore().deleteDirectory(entryDirPath); + + try {Thread.sleep(500L);}catch(Exception ignored){} + } + } + + private void updateFeedDocumentWithNewEntry(Feed f, Entry e) throws AtomException { + boolean inserted = false; + for (int i=0; iRepresents a blog, which has collections of entries and resources. + * You can access the collections using the getCollections() and + * getCollection(String name) methods, which return Blog.Collection objects, + * which you can use to create, retrieve update or delete entries within + * a collection.

+ */ +public interface Blog { + + /** + * Token can be used to fetch this blog again from getBlog() method. + * @return Blog object specified by token. + */ + public String getToken(); + + /** + * Name of this blog. + * @return Display name of this blog. + */ + public String getName(); + + /** + * Get a single BlogEntry (or BlogResource) by token. + * @param token Token from blog entry's getToken() method. + * @throws com.sun.syndication.propono.blogclient.BlogClientException On error fetching the blog entry. + * @return Blog entry specified by token. + */ + public BlogEntry getEntry(String token) throws BlogClientException; + + /** + * Gets listing of entry and resource collections available in the blog, + * including the primary collections. + * @throws BlogClientException On error fetching collections. + * @return List of Blog.Collection objects. + */ + public List getCollections() throws BlogClientException; + + /** + * Get collection by token. + * @param token Token from a collection's getToken() method. + * @throws BlogClientException On error fetching collection. + * @return Blog.Collection object. + */ + public Collection getCollection(String token) throws BlogClientException; + + /** + * Represents an entry or resource collection on a blog server. + */ + public interface Collection { + + /** + * Get blog that contains this collection. + * @return Blog that contains this collection. + */ + public Blog getBlog(); + + /** + * Title of collection. + * @return Title of collecton. + */ + public String getTitle(); + + /** + * Token that can be used to fetch collection. + * @return Token that can be used to fetch collection. + */ + public String getToken(); + + /** + * Content-types accepted by collection. + * @return Comma-separated list of content-types accepted. + */ + public List getAccepts(); + + /** + * Determines if collection will accept a content-type. + * @param contentType Content-type to be considered. + * @return True of content type will be accepted, false otherwise. + */ + public boolean accepts(String contentType); + + /** + * Return categories allowed by colletion. + * @throws BlogClientException On error fetching categories. + * @return List of BlogEntry.Category objects for this collection. + */ + public List getCategories() throws BlogClientException; + + /** + * Create but do not save new entry in collection. + * To save entry, call its save() method. + * @throws BlogClientException On error creating entry. + * @return New BlogEntry object. + */ + public BlogEntry newEntry() throws BlogClientException; + + /** + * Create but do not save new resource in collection. + * To save resource, call its save() method. + * @param name Name of new resource. + * @param contentType MIME content-type of new resource. + * @param bytes Data for new resource. + * @throws BlogClientException On error creating entry. + * @return New BlogResource object, + */ + public BlogResource newResource(String name, String contentType, byte[] bytes) throws BlogClientException; + + /** + * Get iterator over entries/resources in this collection. + * @return List of BlogEntry objects, some may be BlogResources. + * @throws BlogClientException On error fetching entries/resources. + */ + public Iterator getEntries() throws BlogClientException; + + /** + * Save or update a BlogEntry in this collection by adding it to this + * collection and then calling it's entry.save() method. + * @param entry BlogEntry to be saved. + * @throws BlogClientException On error saving entry. + * @return URI of entry. + */ + public String saveEntry(BlogEntry entry) throws BlogClientException; + + /** + * Save or update resource in this collection + * @param resource BlogResource to be saved. + * @throws BlogClientException On error saving resource. + * @return URI of resource. + */ + public String saveResource(BlogResource resource) throws BlogClientException; + } + + + // Deprecated primary collection methods, instead use collections directly. + + /** + * Get iterator over entries in primary entries collection (the first + * collection that accepts entries). Note that entries may be partial, + * so don't try to update and save them: to update and entry, first fetch + * it with getEntry(), change fields, then call entry.save(); + * @return To iterate over all entries in collection. + * @throws BlogClientException On failure or if there is no primary entries collection. + * + * @deprecated Instead use collections directly. + */ + public Iterator getEntries() throws BlogClientException; + + /** + * Get entries in primary resources collection (the first + * collection that accepts anything other than entries). + * @throws BlogClientException On failure or if there is no primary resources collection. + * @return To iterate over all resojrces in collection. + * + * @deprecated Instead use collections directly. + */ + public Iterator getResources() throws BlogClientException; + + + /** + * Create but do not save it to server new BlogEntry in primary entries collection + * (the first collection found that accepts entries). To save the entry to the + * server to a collection, use the entry's save() method. + * @throws BlogClientException On error or if there is no primary entries collection. + * @return Unsaved BlogEntry in primary entries collection. + * + * @deprecated Instead use collections directly. + */ + public BlogEntry newEntry() throws BlogClientException; + + /** + * Create but do not save it to server new BlogResource in primary resources collection + * (the first collection found that accepts resources). To save the resource to the + * server to a collection, use the resource's save() method. + * @param name Name of resource to be saved. + * @param type MIME content type of resource data. + * @param bytes Bytes of resource data. + * @throws BlogClientException On error or if there is no primary respurces collection. + * @return Unsaved BlogEntry in primary resources collection. + * + * @deprecated Instead use collections directly. + */ + public BlogResource newResource(String name, String type, byte[] bytes) + throws BlogClientException; + + /** + * Returns list of available BlogEntry.Category in primary entries collection. + * @throws BlogClientException On error or if there is no primary entries collection. + * @return List of BlogEntry.Category objects. + * + * @deprecated Instead use collections directly. + */ + public List getCategories() throws BlogClientException; +} diff --git a/src/main/java/com/sun/syndication/propono/blogclient/BlogClientException.java b/src/main/java/com/sun/syndication/propono/blogclient/BlogClientException.java new file mode 100644 index 0000000..b982a5a --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/BlogClientException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient; + +/** + * Represents a Blog Client exception, the library throws these instead of + * implementation specific exceptions. + */ +public class BlogClientException extends Exception { + + /** + * Construct a new exception + * @param msg Text message that explains exception + */ + public BlogClientException(String msg) { + super(msg); + } + + /** + * Construct a new exception which wraps a throwable. + * @param msg Text message that explains exception + * @param t Throwable to be wrapped by exception + */ + public BlogClientException(String msg, Throwable t) { + super(msg, t); + } +} diff --git a/src/main/java/com/sun/syndication/propono/blogclient/BlogConnection.java b/src/main/java/com/sun/syndication/propono/blogclient/BlogConnection.java new file mode 100644 index 0000000..e889f9b --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/BlogConnection.java @@ -0,0 +1,34 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient; + +import java.util.List; + +/** + * A BlogConnection is a single-user connection to a blog server where the user + * has access to multiple blogs, which are each represented by a Blog interface. + */ +public interface BlogConnection { + + /** Returns collection of blogs available from this connection */ + public abstract List getBlogs(); + + /** Get blog by token */ + public abstract Blog getBlog(String token); + + /** Set appkey (optional, needed by some blog servers) */ + public void setAppkey(String appkey); +} \ No newline at end of file diff --git a/src/main/java/com/sun/syndication/propono/blogclient/BlogConnectionFactory.java b/src/main/java/com/sun/syndication/propono/blogclient/BlogConnectionFactory.java new file mode 100644 index 0000000..0f9945f --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/BlogConnectionFactory.java @@ -0,0 +1,80 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient; + +import java.lang.reflect.Constructor; + +/** + * Entry point to the Blogapps blog client library. + */ +public class BlogConnectionFactory { + + // BlogConnection implementations must: + // 1) implement BlogConnection + // 2) privide contructor that accepts three strings args: url, username and password. + + // TODO: make implementations configurable + private static String ATOMPROTOCOL_IMPL_CLASS = + "com.sun.syndication.propono.blogclient.atomprotocol.AtomConnection"; + + private static String METAWEBLOG_IMPL_CLASS = + "com.sun.syndication.propono.blogclient.metaweblog.MetaWeblogConnection"; + + /** + * Create a connection to a blog server. + * @param type Connection type, must be "atom" or "metaweblog" + * @param url End-point URL to connect to + * @param username Username for login to blog server + * @param password Password for login to blog server + */ + public static BlogConnection getBlogConnection( + String type, String url, String username, String password) + throws BlogClientException { + BlogConnection blogConnection = null; + if (type == null || type.equals("metaweblog")) { + blogConnection = createBlogConnection( + METAWEBLOG_IMPL_CLASS, url, username, password); + } else if (type.equals("atom")) { + blogConnection = createBlogConnection( + ATOMPROTOCOL_IMPL_CLASS, url, username, password); + } else { + throw new BlogClientException("Type must be 'atom' or 'metaweblog'"); + } + return blogConnection; + } + + private static BlogConnection createBlogConnection( + String className, String url, String username, String password) + throws BlogClientException { + Class conClass; + try { + conClass = Class.forName(className); + } catch (ClassNotFoundException ex) { + throw new BlogClientException( + "BlogConnection impl. class not found: "+className, ex); + } + Class[] args = new Class[] {String.class, String.class, String.class}; + Constructor ctor; + try { + ctor = conClass.getConstructor(args); + return (BlogConnection) + ctor.newInstance(new Object[] {url, username, password}); + } catch (Throwable t) { + throw new BlogClientException( + "ERROR instantiating BlogConnection impl.", t); + } + } +} diff --git a/src/main/java/com/sun/syndication/propono/blogclient/BlogEntry.java b/src/main/java/com/sun/syndication/propono/blogclient/BlogEntry.java new file mode 100644 index 0000000..be79697 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/BlogEntry.java @@ -0,0 +1,231 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient; + +import java.util.Date; +import java.util.List; +import java.io.InputStream; + +/** + * Represents a single blog entry. + */ +public interface BlogEntry { + + /** Get token, which can be used to fetch the blog entry */ + public String getToken(); + + /** + * Save this entry to it's collection. If this is a new entry and does not + * have a collection yet, then save() will save it to the primary collection. + */ + public void save() throws BlogClientException; + + /** Delete this entry from blog server */ + public void delete() throws BlogClientException; + + /** Permanent link to this entry (assigned by server) */ + public String getPermalink(); + + /** Blog is associated with a blog */ + public Blog getBlog(); + + /** Get categories, a list of BlogEntry.Category objects */ + public List getCategories(); + + /** Set categories, a list of BlogEntry.Category objects */ + public void setCategories(List categories); + + /** Get globally unique ID of this blog entry */ + public String getId(); + + /** Get title of this blog entry */ + public String getTitle(); + + /** Set title of this blog entry */ + public void setTitle(String title); + + /** Get summary of this blog entry */ + public String getSummary(); + + /** Set summary of this blog entry */ + public void setSummary(String summary); + + /** Get content of this blog entry */ + public Content getContent(); + + /** Set content of this blog entry */ + public void setContent(Content content); + + /** Get draft status of this entry */ + public boolean getDraft(); + + /** Set draft status of this entry */ + public void setDraft(boolean draft); + + /** Get author of this entry */ + public Person getAuthor(); + + /** Set author of this entry */ + public void setAuthor(Person author); + + /** Set publish date of this entry */ + public Date getPublicationDate(); + + /** Get publish date of this entry */ + public void setPublicationDate(Date date); + + /** Get update date of this entry */ + public Date getModificationDate(); + + /** Set update date of this entry */ + public void setModificationDate(Date date); + + /** Represents blog entry content */ + public class Content { + String type = "html"; + String value = null; + String src = null; + + /** Construct content */ + public Content() {} + + /** Construct content with value (and type="html") */ + public Content(String value) { + this.value = value; + } + /** Get value of content if in-line */ + public String getValue() { + return value; + } + /** Set value of content if in-line */ + public void setValue(String value) { + this.value = value; + } + /** + * Get type of content, either "text", "html", "xhtml" or a MIME content-type. + * Defaults to HTML. + */ + public String getType() { + return type; + } + /** + * Set type of content, either "text", "html", "xhtml" or a MIME content-type. + * Defaults to HTML. + */ + public void setType(String type) { + this.type = type; + } + /** Get URI of content if out-of-line */ + public String getSrc() { + return src; + } + /** Set URI of content if out-of-line */ + public void setSrc(String src) { + this.src = src; + } + } + + /** Represents a blog author or contributor */ + public class Person { + String name; + String email; + String url; + /** Get person's email */ + public String getEmail() { + return email; + } + /** Set person's email */ + public void setEmail(String email) { + this.email = email; + } + /** Get person's name */ + public String getName() { + return name; + } + /** Set person's name */ + public void setName(String name) { + this.name = name; + } + /** Get person's URL */ + public String getUrl() { + return url; + } + /** Set person's URL */ + public void setUrl(String url) { + this.url = url; + } + /** Returns person's name */ + public String toString() { + return name; + } + } + + /** Represents a weblog category */ + public class Category { + String id; + String name; + String url; + /** + * Create new Catetory + */ + public Category() {} + /** + * Create new category with name. + */ + public Category(String id) { + this.id = id; + this.name = id; + } + /** + * Determines if categories are equal based on id. + */ + public boolean equals(Object obj) { + Category other = (Category)obj; + if (obj == null) return false; + if (getId() != null && other.getId() != null + && getId().equals(other.getId())) return true; + return false; + } + /** Get category id */ + public String getId() { + return id; + } + /** Set category id */ + public void setId(String id) { + this.id = id; + } + /** Get category display name */ + public String getName() { + return name; + } + /** Set category display name */ + public void setName(String name) { + this.name = name; + } + /** Get URL of category domain */ + public String getUrl() { + return url; + } + /** Set URL of category domain */ + public void setUrl(String url) { + this.url = url; + } + /** Return category's name or id for display */ + public String toString() { + return name!=null ? name : id; + } + } +} diff --git a/src/main/java/com/sun/syndication/propono/blogclient/BlogResource.java b/src/main/java/com/sun/syndication/propono/blogclient/BlogResource.java new file mode 100644 index 0000000..5e7043e --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/BlogResource.java @@ -0,0 +1,39 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient; + +import java.io.InputStream; + +/** + * Represents a file that has been uploaded to a blog. + *

+ * Resources are modeled as a type of BlogEntry, but be aware: not all servers + * can save resource metadata (i.e. title, category, author, etc.). MetaWeblog + * based servers can't save metadata at all and Atom protocol servers are not + * required to preserve uploaded file metadata. + */ +public interface BlogResource extends BlogEntry { + + /** Get resource name (name is required) */ + public String getName(); + + /** Get resource as stream, using content.src as URL */ + public InputStream getAsStream() throws BlogClientException; + + /** Update resource by immediately uploading new bytes to server */ + public void update(byte[] newBytes) throws BlogClientException; +} + diff --git a/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomBlog.java b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomBlog.java new file mode 100644 index 0000000..9da2b20 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomBlog.java @@ -0,0 +1,206 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient.atomprotocol; + +import com.sun.syndication.propono.utils.ProponoException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.sun.syndication.propono.blogclient.Blog; +import com.sun.syndication.propono.blogclient.BlogClientException; +import com.sun.syndication.propono.blogclient.BlogEntry; +import com.sun.syndication.propono.blogclient.BlogResource; +import com.sun.syndication.propono.atom.client.ClientAtomService; +import com.sun.syndication.propono.atom.client.ClientCollection; +import com.sun.syndication.propono.atom.client.ClientEntry; +import com.sun.syndication.propono.atom.client.ClientMediaEntry; +import com.sun.syndication.propono.atom.client.ClientWorkspace; +import java.util.Map; +import java.util.TreeMap; + +/** + * Atom protocol implementation of the BlogClient Blog interface. + */ +public class AtomBlog implements Blog { + static final Log logger = LogFactory.getLog(AtomBlog.class); + private HttpClient httpClient = null; + private String name = null; + private ClientAtomService service; + private ClientWorkspace workspace = null; + private AtomCollection entriesCollection = null; + private AtomCollection resourcesCollection = null; + private Map collections = new TreeMap(); + + /** + * Create AtomBlog using specified HTTPClient, user account and workspace, + * called by AtomConnection. Fetches Atom Service document and creates + * an AtomCollection object for each collection found. The first entry + * collection is considered the primary entry collection. And the first + * resource collection is considered the primary resource collection. + */ + AtomBlog(ClientAtomService service, ClientWorkspace workspace) { + this.setService(service); + this.setWorkspace(workspace); + this.name = workspace.getTitle(); + Iterator members = workspace.getCollections().iterator(); + + while (members.hasNext()) { + ClientCollection col = (ClientCollection) members.next(); + if (col.accepts("entry") && entriesCollection == null) { + // first entry collection is primary entry collection + entriesCollection = new AtomCollection(this, col); + } + else if (!col.accepts("entry") && resourcesCollection == null) { + // first non-entry collection is primary resource collection + resourcesCollection = new AtomCollection(this, col); + } + collections.put(col.getHrefResolved(), new AtomCollection(this, col)); + } + } + + /** + * {@inheritDoc} + */ + public String getName() { return name; } + + /** + * String display of blog, returns name. + */ + public String toString() { return getName(); } + + /** + * {@inheritDoc} + */ + public String getToken() { return entriesCollection.getToken(); } + + /** + * {@inheritDoc} + */ + public BlogEntry newEntry() throws BlogClientException { + if (entriesCollection == null) throw new BlogClientException("No entry collection"); + return entriesCollection.newEntry(); + } + + /** + * {@inheritDoc} + */ + public BlogEntry getEntry(String token) throws BlogClientException { + ClientEntry clientEntry = null; + AtomEntry atomEntry = null; + try { + clientEntry = getService().getEntry(token); + } catch (ProponoException ex) { + throw new BlogClientException("ERROR: fetching entry", ex); + } + if (clientEntry != null && clientEntry instanceof ClientMediaEntry) { + return new AtomResource(this, (ClientMediaEntry)clientEntry); + } else if (clientEntry != null && clientEntry instanceof ClientEntry) { + return new AtomEntry(this, clientEntry); + } else { + throw new BlogClientException("ERROR: unknown object type returned"); + } + } + + /** + * {@inheritDoc} + */ + public Iterator getEntries() throws BlogClientException { + if (entriesCollection == null) throw new BlogClientException("No primary entry collection"); + return new AtomEntryIterator(entriesCollection); + } + + /** + * {@inheritDoc} + */ + public Iterator getResources() throws BlogClientException { + if (resourcesCollection == null) throw new BlogClientException("No primary entry collection"); + return new AtomEntryIterator(resourcesCollection); + } + + String saveEntry(BlogEntry entry) throws BlogClientException { + if (entriesCollection == null) throw new BlogClientException("No primary entry collection"); + return entriesCollection.saveEntry(entry); + } + + void deleteEntry(BlogEntry entry) throws BlogClientException { + if (entriesCollection == null) throw new BlogClientException("No primary entry collection"); + entriesCollection.deleteEntry(entry); + } + + /** + * {@inheritDoc} + */ + public List getCategories() throws BlogClientException { + if (entriesCollection == null) throw new BlogClientException("No primary entry collection"); + return entriesCollection.getCategories(); + } + + /** + * {@inheritDoc} + */ + public BlogResource newResource( + String name, String contentType, byte[] bytes) throws BlogClientException { + if (resourcesCollection == null) { + throw new BlogClientException("No resource collection"); + } + return resourcesCollection.newResource(name, contentType, bytes); + } + + + String saveResource(BlogResource res) throws BlogClientException { + if (resourcesCollection == null) throw new BlogClientException("No primary resource collection"); + return resourcesCollection.saveResource(res); + } + + void deleteResource(BlogResource resource) throws BlogClientException { + deleteEntry((BlogEntry)resource); + } + + /** + * {@inheritDoc} + */ + public List getCollections() throws BlogClientException { + return new ArrayList(collections.values()); + } + + /** + * {@inheritDoc} + */ + public Blog.Collection getCollection(String token) throws BlogClientException { + return (Blog.Collection)collections.get(token); + } + + ClientAtomService getService() { + return service; + } + + void setService(ClientAtomService service) { + this.service = service; + } + + ClientWorkspace getWorkspace() { + return workspace; + } + + void setWorkspace(ClientWorkspace workspace) { + this.workspace = workspace; + } + +} diff --git a/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomCollection.java b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomCollection.java new file mode 100644 index 0000000..1216317 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomCollection.java @@ -0,0 +1,165 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient.atomprotocol; + +import com.sun.syndication.feed.atom.Category; +import com.sun.syndication.propono.atom.client.ClientAtomService; +import com.sun.syndication.propono.atom.common.Categories; +import com.sun.syndication.propono.atom.client.ClientCollection; +import com.sun.syndication.propono.atom.client.ClientEntry; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.sun.syndication.propono.blogclient.Blog; +import com.sun.syndication.propono.blogclient.BlogClientException; +import com.sun.syndication.propono.blogclient.BlogEntry; +import com.sun.syndication.propono.blogclient.BlogResource; + +/** + * Atom protocol implementation of BlogClient Blog.Collection. + */ +public class AtomCollection implements Blog.Collection { + static final Log logger = LogFactory.getLog(AtomCollection.class); + + private Blog blog = null; + private List categories = new ArrayList(); + + private ClientCollection clientCollection = null; + + + AtomCollection(AtomBlog blog, ClientCollection col) { + this.blog = blog; + this.clientCollection = col; + for (Iterator catsIter = col.getCategories().iterator(); catsIter.hasNext();) { + Categories cats = (Categories)catsIter.next(); + for (Iterator catIter = cats.getCategories().iterator(); catIter.hasNext();) { + Category cat = (Category)catIter.next(); + BlogEntry.Category blogCat = new BlogEntry.Category(cat.getTerm()); + blogCat.setName(cat.getLabel()); + blogCat.setUrl(cat.getScheme()); + getCategories().add(blogCat); + } + } + } + + /** + * {@inheritDoc} + */ + public String getTitle() { + return getClientCollection().getTitle(); + } + + /** + * {@inheritDoc} + */ + public String getToken() { + return getClientCollection().getHrefResolved(); + } + + /** + * {@inheritDoc} + */ + public List getAccepts() { + return getClientCollection().getAccepts(); + } + + /** + * {@inheritDoc} + */ + public boolean accepts(String ct) { + return getClientCollection().accepts(ct); + } + + /** + * {@inheritDoc} + */ + public Iterator getEntries() throws BlogClientException { + return new AtomEntryIterator(this); + } + + /** + * {@inheritDoc} + */ + public BlogEntry newEntry() throws BlogClientException { + AtomBlog atomBlog = (AtomBlog)getBlog(); + BlogEntry entry = new AtomEntry(atomBlog, this); + return entry; + } + + /** + * {@inheritDoc} + */ + public BlogResource newResource(String name, String contentType, byte[] bytes) throws BlogClientException { + return new AtomResource(this, name, contentType, bytes); + } + + /** + * {@inheritDoc} + */ + public String saveResource(BlogResource res) throws BlogClientException { + ((AtomResource)res).setCollection(this); + res.save(); + return res.getContent().getSrc(); + } + + /** + * {@inheritDoc} + */ + public String saveEntry(BlogEntry entry) throws BlogClientException { + ((AtomEntry)entry).setCollection(this); + entry.save(); + return entry.getPermalink(); + } + + void deleteEntry(BlogEntry entry) throws BlogClientException { + try { + ClientAtomService service = ((AtomBlog)getBlog()).getService(); + ClientEntry clientEntry = service.getEntry(entry.getToken()); + clientEntry.remove(); + + } catch (Exception e) { + throw new BlogClientException("ERROR deleting entry", e); + } + } + + /** + * {@inheritDoc} + */ + public Blog getBlog() { + return blog; + } + + void setBlog(AtomBlog blog) { + this.blog = blog; + } + + /** + * {@inheritDoc} + */ + public List getCategories() { + return categories; + } + + void setCategories(List categories) { + this.categories = categories; + } + + ClientCollection getClientCollection() { + return clientCollection; + } +} diff --git a/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomConnection.java b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomConnection.java new file mode 100644 index 0000000..dc483a4 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomConnection.java @@ -0,0 +1,92 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient.atomprotocol; + +import com.sun.syndication.propono.atom.client.AtomClientFactory; +import com.sun.syndication.propono.atom.client.BasicAuthStrategy; +import com.sun.syndication.propono.atom.client.ClientAtomService; +import com.sun.syndication.propono.atom.client.ClientWorkspace; +import java.util.List; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.jdom.Document; + +import com.sun.syndication.propono.blogclient.BlogConnection; +import com.sun.syndication.propono.blogclient.Blog; +import com.sun.syndication.propono.blogclient.BlogClientException; + + +/** + * Atom protocol of BlogConnection. Connects to Atom server, creates AtomBlog + * object for each Atom workspace found and within each blog a collection for each + * Atom collection found. + */ +public class AtomConnection implements BlogConnection { + private static Log logger = LogFactory.getLog(AtomConnection.class); + private HttpClient httpClient = null; + private Map blogs = new HashMap(); + + /** + * Create Atom blog client instance for specified URL and user account. + * @param uri End-point URL of Atom service + * @param username Username of account + * @param password Password of account + */ + public AtomConnection(String uri, String username, String password) + throws BlogClientException { + + Document doc = null; + try { + ClientAtomService service = (ClientAtomService) + AtomClientFactory.getAtomService(uri, new BasicAuthStrategy(username, password)); + Iterator iter = service.getWorkspaces().iterator(); + int count = 0; + while (iter.hasNext()) { + ClientWorkspace workspace = (ClientWorkspace)iter.next(); + Blog blog = new AtomBlog(service, workspace); + blogs.put(blog.getToken(), blog); + } + } catch (Throwable t) { + throw new BlogClientException("Error connecting to blog server", t); + } + } + + /** + * {@inheritDoc} + */ + public List getBlogs() { + return new ArrayList(blogs.values()); + } + + /** + * {@inheritDoc} + */ + public Blog getBlog(String token) { + return (AtomBlog)blogs.get(token); + } + + /** + * {@inheritDoc} + */ + public void setAppkey(String appkey) { + } +} diff --git a/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomEntry.java b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomEntry.java new file mode 100644 index 0000000..59deaef --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomEntry.java @@ -0,0 +1,240 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient.atomprotocol; + +import com.sun.syndication.propono.utils.ProponoException; +import com.sun.syndication.propono.atom.common.rome.AppModule; +import com.sun.syndication.propono.atom.common.rome.AppModuleImpl; +import com.sun.syndication.propono.blogclient.BlogClientException; +import com.sun.syndication.propono.blogclient.BlogEntry; +import com.sun.syndication.propono.blogclient.BaseBlogEntry; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import com.sun.syndication.feed.atom.Entry; +import com.sun.syndication.feed.atom.Link; +import com.sun.syndication.propono.atom.client.ClientEntry; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.sun.syndication.propono.blogclient.BlogEntry.Person; + +/** + * Atom protocol implementation of BlogEntry. + */ +public class AtomEntry extends BaseBlogEntry implements BlogEntry { + static final Log logger = LogFactory.getLog(AtomCollection.class); + + String editURI = null; + AtomCollection collection = null; + + AtomEntry(AtomBlog blog, AtomCollection collection) throws BlogClientException { + super(blog); + this.collection = collection; + } + + AtomEntry(AtomCollection collection, ClientEntry entry) throws BlogClientException { + this((AtomBlog)collection.getBlog(), collection); + //clientEntry = entry; + copyFromRomeEntry(entry); + } + + AtomEntry(AtomBlog blog, ClientEntry entry) throws BlogClientException { + super(blog); + //clientEntry = entry; + copyFromRomeEntry(entry); + } + + /** + * {@inheritDoc} + */ + public String getToken() { + return editURI; + } + + AtomCollection getCollection() { + return collection; + } + + void setCollection(AtomCollection collection) { + this.collection = collection; + } + + /** + * True if entry's token's are equal. + */ + public boolean equals(Object o) { + if (o instanceof AtomEntry) { + AtomEntry other = (AtomEntry)o; + if (other.getToken() != null && getToken() != null) { + return other.getToken().equals(getToken()); + } + } + return false; + } + + /** + * {@inheritDoc} + */ + public void save() throws BlogClientException { + boolean create = (getToken() == null); + if (create && getCollection() == null) { + throw new BlogClientException("Cannot save entry, no collection"); + } else if (create) { + try { + ClientEntry clientEntry = collection.getClientCollection().createEntry(); + copyToRomeEntry(clientEntry); + collection.getClientCollection().addEntry(clientEntry); + copyFromRomeEntry(clientEntry); + } catch (ProponoException ex) { + throw new BlogClientException("Error saving entry", ex); + } + } else { + try { + ClientEntry clientEntry = ((AtomBlog)getBlog()).getService().getEntry(getToken()); + copyToRomeEntry(clientEntry); + clientEntry.update(); + copyFromRomeEntry(clientEntry); + } catch (ProponoException ex) { + throw new BlogClientException("Error updating entry", ex); + } + } + } + + /** + * {@inheritDoc} + */ + public void delete() throws BlogClientException { + if (getToken() == null) { + throw new BlogClientException("Cannot delete unsaved entry"); + } + try { + ClientEntry clientEntry = ((AtomBlog)getBlog()).getService().getEntry(editURI); + clientEntry.remove(); + } catch (ProponoException ex) { + throw new BlogClientException("Error removing entry", ex); + } + } + + void copyFromRomeEntry(ClientEntry entry) { + id = entry.getId(); + title = entry.getTitle(); + editURI = entry.getEditURI(); + List altlinks = entry.getAlternateLinks(); + if (altlinks != null) { + for (Iterator iter = altlinks.iterator(); iter.hasNext();) { + Link link = (Link)iter.next(); + if ("alternate".equals(link.getRel()) || link.getRel()==null) { + permalink = link.getHrefResolved(); + break; + } + } + } + List contents = entry.getContents(); + com.sun.syndication.feed.atom.Content romeContent = null; + if (contents != null && contents.size() > 0) { + romeContent = (com.sun.syndication.feed.atom.Content)contents.get(0); + } + if (romeContent != null) { + content = new BlogEntry.Content(romeContent.getValue()); + content.setType(romeContent.getType()); + content.setSrc(romeContent.getSrc()); + } + if (entry.getCategories() != null) { + List cats = new ArrayList(); + List romeCats = entry.getCategories(); + for (Iterator iter=romeCats.iterator(); iter.hasNext();) { + com.sun.syndication.feed.atom.Category romeCat = + (com.sun.syndication.feed.atom.Category)iter.next(); + BlogEntry.Category cat = new BlogEntry.Category(); + cat.setId(romeCat.getTerm()); + cat.setUrl(romeCat.getScheme()); + cat.setName(romeCat.getLabel()); + cats.add(cat); + } + categories = cats; + } + List authors = entry.getAuthors(); + if (authors!=null && authors.size() > 0) { + com.sun.syndication.feed.atom.Person romeAuthor = + (com.sun.syndication.feed.atom.Person)authors.get(0); + if (romeAuthor != null) { + author = new Person(); + author.setName(romeAuthor.getName()); + author.setEmail(romeAuthor.getEmail()); + author.setUrl(romeAuthor.getUrl()); + } + } + publicationDate = entry.getPublished(); + modificationDate = entry.getModified(); + + AppModule control = (AppModule)entry.getModule(AppModule.URI); + if (control != null && control.getDraft() != null) { + draft = control.getDraft().booleanValue(); + } else { + draft = false; + } + } + Entry copyToRomeEntry(ClientEntry entry) { + if (id != null) { + entry.setId(id); + } + entry.setTitle(title); + if (author != null) { + com.sun.syndication.feed.atom.Person person = + new com.sun.syndication.feed.atom.Person(); + person.setName(author.getName()); + person.setEmail(author.getEmail()); + person.setUrl(author.getUrl()); + List authors = new ArrayList(); + authors.add(person); + entry.setAuthors(authors); + } + if (content != null) { + com.sun.syndication.feed.atom.Content romeContent = + new com.sun.syndication.feed.atom.Content(); + romeContent.setValue(content.getValue()); + romeContent.setType(content.getType()); + List contents = new ArrayList(); + contents.add(romeContent); + entry.setContents(contents); + } + if (categories != null) { + List romeCats = new ArrayList(); + for (Iterator iter=categories.iterator(); iter.hasNext();) { + BlogEntry.Category cat = (BlogEntry.Category)iter.next(); + com.sun.syndication.feed.atom.Category romeCategory = + new com.sun.syndication.feed.atom.Category(); + romeCategory.setTerm(cat.getId()); + romeCategory.setScheme(cat.getUrl()); + romeCategory.setLabel(cat.getName()); + romeCats.add(romeCategory); + } + entry.setCategories(romeCats); + } + entry.setPublished((publicationDate == null) ? new Date() : publicationDate); + entry.setModified((modificationDate == null) ? new Date() : modificationDate); + + List modules = new ArrayList(); + AppModule control = new AppModuleImpl(); + control.setDraft(new Boolean(draft)); + modules.add(control); + entry.setModules(modules); + + return entry; + } + +} diff --git a/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomEntryIterator.java b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomEntryIterator.java new file mode 100644 index 0000000..9191c15 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomEntryIterator.java @@ -0,0 +1,72 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient.atomprotocol; + +import com.sun.syndication.propono.atom.client.ClientEntry; +import com.sun.syndication.propono.atom.client.ClientMediaEntry; +import java.util.Iterator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import com.sun.syndication.propono.blogclient.BlogClientException; + +/** + * Atom protocol implementation of BlogClient entry iterator. + */ +public class AtomEntryIterator implements Iterator { + static final Log logger = LogFactory.getLog(AtomEntryIterator.class); + private Iterator iterator = null; + private AtomCollection collection = null; + + AtomEntryIterator(AtomCollection collection) throws BlogClientException { + try { + this.collection = collection; + iterator = collection.getClientCollection().getEntries(); + } catch (Exception e) { + throw new BlogClientException("ERROR fetching collection", e); + } + } + + /** + * True if more entries are available. + */ + public boolean hasNext() { + return iterator.hasNext(); + } + + /** + * Get next entry. + */ + public Object next() { + try { + ClientEntry entry = (ClientEntry)iterator.next(); + if (entry instanceof ClientMediaEntry) { + return new AtomResource(collection, (ClientMediaEntry)entry); + } else { + return new AtomEntry(collection, entry); + } + } catch (Exception e) { + logger.error("ERROR fetching entry", e); + } + return null; + } + + /** + * Remove is not supported. + */ + public void remove() { + // optional method, not implemented + } +} diff --git a/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomResource.java b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomResource.java new file mode 100644 index 0000000..455cb9e --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/atomprotocol/AtomResource.java @@ -0,0 +1,134 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient.atomprotocol; + +import java.io.InputStream; + +import com.sun.syndication.propono.blogclient.BlogClientException; +import com.sun.syndication.propono.blogclient.BlogEntry; +import com.sun.syndication.propono.blogclient.BlogResource; +import com.sun.syndication.feed.atom.Link; +import com.sun.syndication.propono.atom.client.ClientAtomService; +import com.sun.syndication.propono.atom.client.ClientCollection; +import com.sun.syndication.propono.atom.client.ClientEntry; +import com.sun.syndication.propono.atom.client.ClientMediaEntry; +import java.util.Iterator; +import java.util.List; + +/** + * Atom protocol implementation of BlogResource. + */ +public class AtomResource extends AtomEntry implements BlogResource { + private AtomCollection collection; + private byte[] bytes; + + AtomResource(AtomCollection collection, String name, String contentType, byte[] bytes) + throws BlogClientException { + super((AtomBlog)collection.getBlog(), collection); + this.collection = collection; + this.bytes = bytes; + BlogEntry.Content rcontent = new BlogEntry.Content(); + rcontent.setType(contentType); + setContent(rcontent); + } + + AtomResource(AtomCollection collection, ClientMediaEntry entry) + throws BlogClientException { + super(collection, entry); + } + + AtomResource(AtomBlog blog, ClientMediaEntry entry) throws BlogClientException { + super(blog, entry); + } + + /** + * {@inheritDoc} + */ + public String getName() { + return getTitle(); + } + + byte[] getBytes() { + return bytes; + } + + /** + * {@inheritDoc} + */ + public InputStream getAsStream() throws BlogClientException { + try { + return null; //((ClientMediaEntry)clientEntry).getAsStream(); + } catch (Exception e) { + throw new BlogClientException("Error creating entry", e); + } + } + + /** + * {@inheritDoc} + */ + public void save() throws BlogClientException { + try { + if (getToken() == null) { + ClientAtomService clientService = ((AtomBlog)getBlog()).getService(); + ClientCollection clientCollection = collection.getClientCollection(); + + ClientMediaEntry clientEntry = + new ClientMediaEntry(clientService, clientCollection, getTitle(), + "", getContent().getType(), getBytes()); + + copyToRomeEntry(clientEntry); + collection.getClientCollection().addEntry(clientEntry); + this.editURI = clientEntry.getEditURI(); + + } else { + ClientAtomService clientService = ((AtomBlog)getBlog()).getService(); + ClientMediaEntry clientEntry = (ClientMediaEntry)clientService.getEntry(editURI); + clientEntry.update(); + } + } catch (Exception e) { + throw new BlogClientException("Error creating entry", e); + } + } + + /** + * {@inheritDoc} + */ + public void update(byte[] newBytes) throws BlogClientException { + try { + //((ClientMediaEntry)clientEntry).setBytes(newBytes); + //clientEntry.update(); + } catch (Exception e) { + throw new BlogClientException("Error creating entry", e); + } + } + + void copyFromRomeEntry(ClientEntry entry) { + super.copyFromRomeEntry(entry); + + List links = entry.getOtherLinks(); + if (links != null) { + for (Iterator iter = links.iterator(); iter.hasNext();) { + Link link = (Link)iter.next(); + if ("edit-media".equals(link.getRel())) { + id = link.getHrefResolved(); + break; + } + } + } + + + } +} diff --git a/src/main/java/com/sun/syndication/propono/blogclient/blogclient-diagram.gif b/src/main/java/com/sun/syndication/propono/blogclient/blogclient-diagram.gif new file mode 100644 index 0000000000000000000000000000000000000000..35dac526bed67592e2a4feec37d8976085639158 GIT binary patch literal 18568 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%0D!jTfva(!C|1XZW%AL+Rc88JL0Y&Xby6>Ur|zR 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 XQbb8sAT=&92?75s006xx0000xKC_ZD literal 0 HcmV?d00001 diff --git a/src/main/java/com/sun/syndication/propono/blogclient/metaweblog/MetaWeblogBlog.java b/src/main/java/com/sun/syndication/propono/blogclient/metaweblog/MetaWeblogBlog.java new file mode 100644 index 0000000..5c531b3 --- /dev/null +++ b/src/main/java/com/sun/syndication/propono/blogclient/metaweblog/MetaWeblogBlog.java @@ -0,0 +1,436 @@ +/* + * Copyright 2007 Dave Johnson (Blogapps project) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient.metaweblog; + +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Iterator; +import com.sun.syndication.propono.blogclient.BlogEntry; +import com.sun.syndication.propono.blogclient.Blog; +import com.sun.syndication.propono.blogclient.BlogClientException; +import com.sun.syndication.propono.blogclient.BlogResource; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; +import org.apache.xmlrpc.client.XmlRpcClient; +import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; + +/** + * Blog implementation that uses a mix of Blogger and MetaWeblog API methods. + */ +public class MetaWeblogBlog implements Blog { + private String blogid; + private String name; + private URL url; + private String userName; + private String password; + private String appkey = "dummy"; + private Map collections; + + private XmlRpcClient xmlRpcClient = null; + + /** + * {@inheritDoc} + */ + public String getName() { return name; } + + /** + * {@inheritDoc} + */ + public String getToken() { return blogid; } + + /** + * String representation of blog, returns the name. + */ + public String toString() { return getName(); } + + private XmlRpcClient getXmlRpcClient() { + + if (xmlRpcClient == null) { + XmlRpcClientConfigImpl xmlrpcConfig = new XmlRpcClientConfigImpl(); + xmlrpcConfig.setServerURL(url); + xmlRpcClient = new XmlRpcClient(); + xmlRpcClient.setConfig(xmlrpcConfig); + } + return xmlRpcClient; + } + + MetaWeblogBlog(String blogid, String name, + URL url, String userName, String password) { + this.blogid = blogid; + this.name = name; + this.url = url; + this.userName = userName; + this.password = password; + this.collections = new TreeMap(); + collections.put("entries", + new MetaWeblogBlogCollection(this, "entries", "Entries", "entry")); + collections.put("resources", + new MetaWeblogBlogCollection(this, "resources", "Resources", "*")); + } + + MetaWeblogBlog(String blogId, String name, + URL url, String userName, String password, String appkey) { + this(blogId, name, url, userName, password); + this.appkey = appkey; + } + + /** + * {@inheritDoc} + */ + public BlogEntry newEntry() { + return new MetaWeblogEntry(this, new HashMap()); + } + + String saveEntry(BlogEntry entry) throws BlogClientException { + Blog.Collection col = (Blog.Collection)collections.get("entries"); + return col.saveEntry(entry); + } + + /** + * {@inheritDoc} + */ + public BlogEntry getEntry(String id) throws BlogClientException { + try { + Map result = (Map) + getXmlRpcClient().execute("metaWeblog.getPost", new Object[] {id, userName, password}); + return new MetaWeblogEntry(this, result); + } catch (Exception e) { + throw new BlogClientException("ERROR: XML-RPC error getting entry", e); + } + } + + void deleteEntry(String id) throws BlogClientException { + try { + getXmlRpcClient().execute("blogger.deletePost", + new Object[] {appkey, id, userName, password, Boolean.FALSE}); + } catch (Exception e) { + throw new BlogClientException("ERROR: XML-RPC error getting entry", e); + } + } + + /** + * {@inheritDoc} + */ + public Iterator getEntries() throws BlogClientException { + return new EntryIterator(); + } + + /** + * {@inheritDoc} + */ + public BlogResource newResource(String name, String contentType, byte[] bytes) throws BlogClientException { + return new MetaWeblogResource(this, name, contentType, bytes); + } + + String saveResource(MetaWeblogResource resource) throws BlogClientException { + Blog.Collection col = (Blog.Collection)collections.get("resources"); + return col.saveResource(resource); + } + + BlogResource getResource(String token) throws BlogClientException { + return null; + } + + /** + * {@inheritDoc} + */ + public Iterator getResources() throws BlogClientException { + return new NoOpIterator(); + } + + void deleteResource(BlogResource resource) throws BlogClientException { + // no-op + } + + /** + * {@inheritDoc} + */ + public List getCategories() throws BlogClientException { + + ArrayList ret = new ArrayList(); + try { + Object result = + getXmlRpcClient().execute ("metaWeblog.getCategories", + new Object[] {blogid, userName, password}); + if (result != null && result instanceof HashMap) { + // Standard MetaWeblog API style: struct of struts + Map catsmap = (Map)result; + Iterator keys = catsmap.keySet().iterator(); + while (keys.hasNext()) { + String key = (String)keys.next(); + Map catmap = (Map)catsmap.get(key); + BlogEntry.Category category = new BlogEntry.Category(key); + category.setName((String)catmap.get("description")); + // catmap.get("htmlUrl"); + // catmap.get("rssUrl"); + ret.add(category); + } + } else if (result != null && result instanceof Object[]) { + // Wordpress style: array of structs + Object[] resultArray = (Object[])result; + for (int i=0; i 0) { + List catArray = new ArrayList(); + List cats = getCategories(); + for (int i=0; i Integer.MAX_VALUE) { + // File is too large + } + + // Create the byte array to hold the data + byte[] bytes = new byte[(int)length]; + + // Read in the bytes + int offset = 0; + int numRead = 0; + while (offset < bytes.length + && (numRead=is.read(bytes, offset, bytes.length-offset)) >= 0) { + offset += numRead; + } + + // Ensure all the bytes have been read in + if (offset < bytes.length) { + throw new IOException("Could not completely read file "+file.getName()); + } + + // Close the input stream and return bytes + is.close(); + return bytes; + } + + /** + * Read input from stream and into string. + */ + public static String streamToString(InputStream is) throws IOException { + StringBuffer sb = new StringBuffer(); + BufferedReader in = new BufferedReader(new InputStreamReader(is)); + String line; + while ((line = in.readLine()) != null) { + sb.append(line); + sb.append(LS); + } + return sb.toString(); + } + + /** + * Copy input stream to output stream using 8K buffer. + */ + public static void copyInputToOutput( + InputStream input, + OutputStream output) + throws IOException { + BufferedInputStream in = new BufferedInputStream(input); + BufferedOutputStream out = new BufferedOutputStream(output); + byte buffer[] = new byte[8192]; + for (int count = 0; count != -1;) { + count = in.read(buffer, 0, 8192); + if (count != -1) + out.write(buffer, 0, count); + } + + try { + in.close(); + out.close(); + } catch (IOException ex) { + throw new IOException("Closing file streams, " + ex.getMessage()); + } + } + + + /** + * Replaces occurences of non-alphanumeric characters with a supplied char. + */ + public static String replaceNonAlphanumeric(String str, char subst) { + StringBuffer ret = new StringBuffer(str.length()); + char[] testChars = str.toCharArray(); + for (int i = 0; i < testChars.length; i++) { + if (Character.isLetterOrDigit(testChars[i])) { + ret.append(testChars[i]); + } else { + ret.append( subst ); + } + } + return ret.toString(); + } + + /** + * Convert string to string array. + */ + public static String[] stringToStringArray(String instr, String delim) + throws NoSuchElementException, NumberFormatException { + StringTokenizer toker = new StringTokenizer(instr, delim); + String stringArray[] = new String[toker.countTokens()]; + int i = 0; + while (toker.hasMoreTokens()) { + stringArray[i++] = toker.nextToken(); + } + return stringArray; + } + + /** + * Convert string array to string. + */ + public static String stringArrayToString(String[] stringArray, String delim) { + String ret = ""; + for (int i = 0; i < stringArray.length; i++) { + if (ret.length() > 0) + ret = ret + delim + stringArray[i]; + else + ret = stringArray[i]; + } + return ret; + } + + + static Pattern absoluteURIPattern = Pattern.compile("^[a-z0-9]*:.*$"); + + private static boolean isAbsoluteURI(String uri) { + return absoluteURIPattern.matcher(uri).find(); + } + + private static boolean isRelativeURI(String uri) { + return !isAbsoluteURI(uri); + } + + /** + * } + * Resolve URI based considering xml:base and baseURI. + * @param baseURI Base URI of feed + * @param parent Parent from which to consider xml:base + * @param url URL to be resolved + */ + private static String resolveURI(String baseURI, Parent parent, String url) { + if (isRelativeURI(url)) { + url = (!".".equals(url) && !"./".equals(url)) ? url : ""; + + // Relative URI with parent + if (parent != null && parent instanceof Element) { + + // Do we have an xml:base? + String xmlbase = ((Element)parent).getAttributeValue( + "base", Namespace.XML_NAMESPACE); + if (xmlbase != null && xmlbase.trim().length() > 0) { + if (isAbsoluteURI(xmlbase)) { + // Absolute xml:base, so form URI right now + if (url.startsWith("/")) { + // Host relative URI + int slashslash = xmlbase.indexOf("//"); + int nextslash = xmlbase.indexOf("/", slashslash + 2); + if (nextslash != -1) xmlbase = xmlbase.substring(0, nextslash); + return formURI(xmlbase, url); + } + if (!xmlbase.endsWith("/")) { + // Base URI is filename, strip it off + xmlbase = xmlbase.substring(0, xmlbase.lastIndexOf("/")); + } + return formURI(xmlbase, url); + } else { + // Relative xml:base, so walk up tree + return resolveURI(baseURI, parent.getParent(), + stripTrailingSlash(xmlbase) + "/"+ stripStartingSlash(url)); + } + } + // No xml:base so walk up tree + return resolveURI(baseURI, parent.getParent(), url); + + // Relative URI with no parent (i.e. top of tree), so form URI right now + } else if (parent == null || parent instanceof Document) { + return formURI(baseURI, url); + } + } + return url; + } + + /** + * Form URI by combining base with append portion and giving + * special consideration to append portions that begin with ".." + * @param base Base of URI, may end with trailing slash + * @param append String to append, may begin with slash or ".." + */ + private static String formURI(String base, String append) { + base = stripTrailingSlash(base); + append = stripStartingSlash(append); + if (append.startsWith("..")) { + String ret = null; + String[] parts = append.split("/"); + for (int i=0; i 0); + for (Iterator it = service.getWorkspaces().iterator(); it.hasNext();) { + ClientWorkspace space = (ClientWorkspace) it.next(); + assertNotNull(space.getTitle()); + log.debug("Workspace: " + space.getTitle()); + for (Iterator colit = space.getCollections().iterator(); colit.hasNext();) { + ClientCollection col = (ClientCollection) colit.next(); + log.debug(" Collection: " + col.getTitle() + " Accepts: " + col.getAccepts()); + log.debug(" href: " + col.getHrefResolved()); + assertNotNull(col.getTitle()); + } + } + } + + /** + * Tests that entries can be posted and removed in all collections that + * accept entries. Fails if no collections found that accept entries. + */ + public void testSimpleEntryPostAndRemove() throws Exception { + assertNotNull(service); + assertTrue(service.getWorkspaces().size() > 0); + int count = 0; + for (Iterator it = service.getWorkspaces().iterator(); it.hasNext();) { + ClientWorkspace space = (ClientWorkspace) it.next(); + assertNotNull(space.getTitle()); + + for (Iterator colit = space.getCollections().iterator(); colit.hasNext();) { + ClientCollection col = (ClientCollection) colit.next(); + if (col.accepts(Collection.ENTRY_TYPE)) { + + // we found a collection that accepts entries, so post one + ClientEntry m1 = col.createEntry(); + m1.setTitle("Test post"); + Content c = new Content(); + c.setValue("This is a test post"); + c.setType("html"); + m1.setContent(c); + + col.addEntry(m1); + + // entry should now exist on server + ClientEntry m2 = col.getEntry(m1.getEditURI()); + assertNotNull(m2); + + // remove entry + m2.remove(); + + // fetching entry now should result in exception + boolean failed = false; + try { + col.getEntry(m1.getEditURI()); + } catch (ProponoException e) { + failed = true; + } + assertTrue(failed); + count++; + } + } + } + assertTrue(count > 0); + } + + /** + * Tests that entries can be posted, updated and removed in all collections that + * accept entries. Fails if no collections found that accept entries. + */ + public void testSimpleEntryPostUpdateAndRemove() throws Exception { + assertNotNull(service); + assertTrue(service.getWorkspaces().size() > 0); + int count = 0; + for (Iterator it = service.getWorkspaces().iterator(); it.hasNext();) { + ClientWorkspace space = (ClientWorkspace) it.next(); + assertNotNull(space.getTitle()); + + for (Iterator colit = space.getCollections().iterator(); colit.hasNext();) { + ClientCollection col = (ClientCollection) colit.next(); + if (col.accepts(Collection.ENTRY_TYPE)) { + + // we found a collection that accepts entries, so post one + ClientEntry m1 = col.createEntry(); + m1.setTitle(col.getTitle() + ": Test post"); + Content c = new Content(); + c.setValue("This is a test post"); + c.setType("html"); + m1.setContent(c); + + col.addEntry(m1); + + // entry should now exist on server + ClientEntry m2 = (ClientEntry)col.getEntry(m1.getEditURI()); + assertNotNull(m2); + + m2.setTitle(col.getTitle() + ": Updated title"); + m2.update(); + + // entry should now be updated on server + ClientEntry m3 = (ClientEntry)col.getEntry(m1.getEditURI()); + assertEquals(col.getTitle() + ": Updated title", m3.getTitle()); + + // remove entry + m3.remove(); + + // fetching entry now should result in exception + boolean failed = false; + try { + col.getEntry(m1.getEditURI()); + } catch (ProponoException e) { + failed = true; + } + assertTrue(failed); + count++; + } + } + } + assertTrue(count > 0); + } + + public void testFindWorkspace() throws Exception { + assertNotNull(service); + ClientWorkspace ws = (ClientWorkspace)service.findWorkspace("adminblog"); + if (ws != null) { + ClientCollection col = (ClientCollection)ws.findCollection(null, "entry"); + ClientEntry entry = col.createEntry(); + entry.setTitle("NPE on submitting order query"); + entry.setContent("This is a bad one!", Content.HTML); + col.addEntry(entry); + + // entry should now exist on server + ClientEntry saved = (ClientEntry)col.getEntry(entry.getEditURI()); + assertNotNull(saved); + + // remove entry + saved.remove(); + + // fetching entry now should result in exception + boolean failed = false; + try { + col.getEntry(saved.getEditURI()); + } catch (ProponoException e) { + failed = true; + } + assertTrue(failed); + } + } + + /** + * Test posting an entry to every available collection with a fixed and + * an unfixed category if server support allows, then cleanup. + */ + public void testEntryPostWithCategories() throws Exception { + assertNotNull(service); + assertTrue(service.getWorkspaces().size() > 0); + int count = 0; + for (Iterator it = service.getWorkspaces().iterator(); it.hasNext();) { + ClientWorkspace space = (ClientWorkspace) it.next(); + assertNotNull(space.getTitle()); + + for (Iterator colit = space.getCollections().iterator(); colit.hasNext();) { + ClientCollection col = (ClientCollection) colit.next(); + if (col.accepts(Collection.ENTRY_TYPE)) { + + // we found a collection that accepts GIF, so post one + ClientEntry m1 = col.createEntry(); + m1.setTitle("Test post"); + Content c = new Content(); + c.setValue("This is a test post"); + c.setType("html"); + m1.setContent(c); + + // if possible, pick one fixed an un unfixed category + Category fixedCat = null; + Category unfixedCat = null; + List entryCats = new ArrayList(); + for (int i=0; i 0); + } + + /** + * Post media entry to every media colletion avialable on server, then cleanup. + */ + public void testMediaPost() throws Exception { + assertNotNull(service); + assertTrue(service.getWorkspaces().size() > 0); + int count = 0; + for (Iterator it = service.getWorkspaces().iterator(); it.hasNext();) { + ClientWorkspace space = (ClientWorkspace) it.next(); + assertNotNull(space.getTitle()); + + for (Iterator colit = space.getCollections().iterator(); colit.hasNext();) { + ClientCollection col = (ClientCollection) colit.next(); + if (col.accepts("image/gif")) { + + // we found a collection that accepts GIF, so post one + ClientMediaEntry m1 = col.createMediaEntry("duke"+count, "duke"+count, "image/gif", + new FileInputStream("test/testdata/duke-wave-shadow.gif")); + col.addEntry(m1); + + // entry should now exist on server + ClientMediaEntry m2 = (ClientMediaEntry)col.getEntry(m1.getEditURI()); + assertNotNull(m2); + + // remove entry + m2.remove(); + + // fetching entry now should result in exception + boolean failed = false; + try { + col.getEntry(m1.getEditURI()); + } catch (ProponoException e) { + failed = true; + } + assertTrue(failed); + count++; + } + } + } + assertTrue(count > 0); + } + + /** + * Post X media entries each media collection found, test paging, then cleanup. + * + public void testMediaPaging() throws Exception { + ClientAtomService service = getClientAtomService(); + assertNotNull(service); + assertTrue(service.getWorkspaces().size() > 0); + int count = 0; + for (Iterator it = service.getWorkspaces().iterator(); it.hasNext();) { + ClientWorkspace space = (ClientWorkspace) it.next(); + assertNotNull(space.getTitle()); + + for (Iterator colit = space.getCollections().iterator(); colit.hasNext();) { + ClientCollection col = (ClientCollection) colit.next(); + if (col.accepts("image/gif")) { + + // we found a collection that accepts GIF, so post 100 of them + List posted = new ArrayList(); + for (int i=0; i= maxPagingEntries); + count++; + break; + } + } + } + assertTrue(count > 0); + }*/ +} + + diff --git a/src/test/java/com/sun/syndication/propono/atom/client/BloggerDotComTest.java b/src/test/java/com/sun/syndication/propono/atom/client/BloggerDotComTest.java new file mode 100644 index 0000000..42b7ef1 --- /dev/null +++ b/src/test/java/com/sun/syndication/propono/atom/client/BloggerDotComTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.client; + +import com.sun.syndication.feed.atom.Content; +import java.util.Iterator; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Simple APP test designed to run against Blogger.com. + */ +public class BloggerDotComTest extends TestCase { + + private String collectionURI = "http://www.blogger.com/feeds/BLOGID/posts/default"; + private String atomServiceURI= "http://www.blogger.com/feeds/default/blogs?alt=atom-service"; + private String email = "EMAIL"; + private String password = "PASSWORD"; + + public BloggerDotComTest(String testName) { + super(testName); + } + + protected void setUp() throws Exception { + } + + protected void tearDown() throws Exception { + } + + public static Test suite() { + TestSuite suite = new TestSuite(BloggerDotComTest.class); + return suite; + } + + /** + * Verify that server returns service document containing workspaces containing collections. + */ + public void testGetEntries() throws Exception { + + // no auth necessary for iterating through entries + ClientCollection col = AtomClientFactory.getCollection(collectionURI, + new GDataAuthStrategy(email, password, "blogger")); + assertNotNull(col); + int count = 0; + for (Iterator it = col.getEntries(); it.hasNext();) { + ClientEntry entry = (ClientEntry) it.next(); + assertNotNull(entry); + count++; + } + assertTrue(count > 0); + + col = AtomClientFactory.getCollection(collectionURI, + new GDataAuthStrategy(email, password, "blogger")); + ClientEntry p1 = col.createEntry(); + p1.setTitle("Propono post"); + Content c = new Content(); + c.setValue("This is content from ROME Propono"); + p1.setContent(c); + col.addEntry(p1); + + ClientEntry p2 = col.getEntry(p1.getEditURI()); + assertNotNull(p2); + + + ClientAtomService atomService = AtomClientFactory.getAtomService( + collectionURI, new GDataAuthStrategy(email, password, "blogger")); + assertNotNull(atomService); + + } +} + + diff --git a/src/test/java/com/sun/syndication/propono/atom/common/AtomServiceTest.java b/src/test/java/com/sun/syndication/propono/atom/common/AtomServiceTest.java new file mode 100644 index 0000000..ef9d54a --- /dev/null +++ b/src/test/java/com/sun/syndication/propono/atom/common/AtomServiceTest.java @@ -0,0 +1,156 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.common; + +import com.sun.syndication.feed.atom.Category; +import java.io.FileInputStream; +import java.util.Iterator; +import junit.framework.*; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.input.SAXBuilder; + +/** + * Tests reading and writing of service document, no server needed. + */ +public class AtomServiceTest extends TestCase { + + public AtomServiceTest(String testName) { + super(testName); + } + + protected void setUp() throws Exception { + } + + protected void tearDown() throws Exception { + } + + public static Test suite() { + TestSuite suite = new TestSuite(AtomServiceTest.class); + + return suite; + } + + /** + * Test of documentToService method, of class AtomService. + */ + public void testDocumentToService() { + try { + // Load service document from disk + SAXBuilder builder = new SAXBuilder(); + Document document = builder.build(new FileInputStream("test/testdata/servicedoc1.xml")); + assertNotNull(document); + AtomService service = AtomService.documentToService(document); + + int workspaceCount = 0; + + // Verify that service contains expected workspaces, collections and categories + for (Iterator it = service.getWorkspaces().iterator(); it.hasNext();) { + Workspace space = (Workspace)it.next(); + assertNotNull(space.getTitle()); + workspaceCount++; + int collectionCount = 0; + + for (Iterator colit = space.getCollections().iterator(); colit.hasNext();) { + Collection col = (Collection)colit.next(); + assertNotNull(col.getTitle()); + assertNotNull(col.getHrefResolved()); + collectionCount++; + int catCount = 0; + if (col.getCategories().size() > 0) { + for (Iterator catsit = col.getCategories().iterator(); catsit.hasNext();) { + Categories cats = (Categories) catsit.next(); + for (Iterator catit = cats.getCategories().iterator(); catit.hasNext();) { + Category cat = (Category) catit.next(); + catCount++; + } + assertTrue(catCount > 0); + } + } + } + } + + assertTrue(workspaceCount > 0); + + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + /** + * Test of documentToService method, of class AtomService. + */ + public void testServiceToDocument() { + try { + // Create service with workspace and collections + AtomService service = new AtomService(); + + Workspace workspace1 = new Workspace("workspace1", null); + Workspace workspace2 = new Workspace("workspace1", null); + service.addWorkspace(workspace1); + service.addWorkspace(workspace2); + + Collection collection11 = + new Collection("collection11", null, "http://example.com/app/col11"); + Collection collection12 = + new Collection("collection12", null, "http://example.com/app/col12"); + workspace1.addCollection(collection11); + workspace1.addCollection(collection12); + + Collection collection21 = + new Collection("collection21", null, "http://example.com/app/col21"); + Collection collection22 = + new Collection("collection22", null, "http://example.com/app/col22"); + workspace2.addCollection(collection21); + workspace2.addCollection(collection22); + + // TODO: add categories at collection level + + // Convert to JDOM document + Document document = service.serviceToDocument(); + + // verify that JDOM document contains service, workspace and collection + assertEquals("service", document.getRootElement().getName()); + int workspaceCount = 0; + for (Iterator spaceit = document.getRootElement().getChildren().iterator(); spaceit.hasNext();) { + Element elem = (Element) spaceit.next(); + if ("workspace".equals(elem.getName())) { + workspaceCount++; + } + boolean workspaceTitle = false; + int collectionCount = 0; + for (Iterator colit = elem.getChildren().iterator(); colit.hasNext();) { + Element colelem = (Element) colit.next(); + if ("title".equals(colelem.getName())) { + workspaceTitle = true; + } else if ("collection".equals(colelem.getName())){ + collectionCount++; + } + + // TODO: test for categories at the collection level + } + assertTrue(workspaceTitle); + assertTrue(collectionCount > 0); + } + assertTrue(workspaceCount > 0); + + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } +} diff --git a/src/test/java/com/sun/syndication/propono/atom/common/CollectionTest.java b/src/test/java/com/sun/syndication/propono/atom/common/CollectionTest.java new file mode 100644 index 0000000..969731f --- /dev/null +++ b/src/test/java/com/sun/syndication/propono/atom/common/CollectionTest.java @@ -0,0 +1,67 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.atom.common; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import junit.framework.*; + +/** + * Tests Collection class, no server needed. + */ +public class CollectionTest extends TestCase { + + public CollectionTest(String testName) { + super(testName); + } + + protected void setUp() throws Exception { + } + + protected void tearDown() throws Exception { + } + + /** + * Test of accepts method, of class com.sun.syndication.propono.atom.common.Collection. + */ + public void testAccepts() { + + Collection col = + new Collection("dummy_title","dummy_titletype","dummy_href"); + + col.setAccepts(Collections.singletonList("image/*")); + assertTrue(col.accepts("image/gif")); + assertTrue(col.accepts("image/jpg")); + assertTrue(col.accepts("image/png")); + assertFalse(col.accepts("test/html")); + + List accepts = new ArrayList(); + accepts.add("image/*"); + accepts.add("text/*"); + col.setAccepts(accepts); + assertTrue(col.accepts("image/gif")); + assertTrue(col.accepts("image/jpg")); + assertTrue(col.accepts("image/png")); + assertTrue(col.accepts("text/html")); + + col.setAccepts(Collections.singletonList("*/*")); + assertTrue(col.accepts("image/gif")); + assertTrue(col.accepts("image/jpg")); + assertTrue(col.accepts("image/png")); + assertTrue(col.accepts("text/html")); + } +} diff --git a/src/test/java/com/sun/syndication/propono/atom/server/AtomClientServerTest.java b/src/test/java/com/sun/syndication/propono/atom/server/AtomClientServerTest.java new file mode 100644 index 0000000..4179cb8 --- /dev/null +++ b/src/test/java/com/sun/syndication/propono/atom/server/AtomClientServerTest.java @@ -0,0 +1,122 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.sun.syndication.propono.atom.server; + +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import junit.framework.Test; +import junit.framework.TestSuite; +import org.mortbay.http.HttpContext; +import org.mortbay.http.HttpServer; +import org.mortbay.http.SocketListener; +import org.mortbay.jetty.servlet.ServletHandler; + + +/** + * Test Propono Atom Client against Atom Server via Jetty. Extends + * AtomClientTest to start Jetty server, run tests and then stop + * the Jetty server. + */ +public class AtomClientServerTest { // extends AtomClientTest { + + private HttpServer server; + public static final int TESTPORT = 8283; + public static final String ENDPOINT = "http://localhost:" + TESTPORT + "/rome/app"; + public static final String USERNAME = "admin"; + public static final String PASSWORD = "admin"; + + public AtomClientServerTest(String s) { + //super(s); + } + + public String getEndpoint() { + return ENDPOINT; + } + + public String getUsername() { + return USERNAME; + } + + public String getPassword() { + return PASSWORD; + } + + public static Test suite() { + TestSuite suite = new TestSuite(AtomClientServerTest.class); + return suite; + } + + protected HttpServer getServer() { + return server; + } + + protected void setUp() throws Exception { + ConsoleHandler handler = new ConsoleHandler(); + Logger logger = Logger.getLogger("com.sun.syndication.propono"); + logger.setLevel(Level.FINEST); + logger.addHandler(handler); + + setupServer(); + HttpContext context = createContext(); + ServletHandler servlets = createServletHandler(); + context.addHandler(servlets); + server.addContext(context); + server.start(); + } + + private void setupServer() throws InterruptedException { + // Create the server + if (server != null) { + server.stop(); + server = null; + } + server = new HttpServer(); + + // Create a port listener + SocketListener listener = new SocketListener(); + listener.setPort(TESTPORT); + server.addListener(listener); + } + + private ServletHandler createServletHandler() { + System.setProperty( + "com.sun.syndication.propono.atom.server.AtomHandlerFactory", + "com.sun.syndication.propono.atom.server.TestAtomHandlerFactory"); + ServletHandler servlets = new ServletHandler(); + servlets.addServlet( + "app", "/app/*", + "com.sun.syndication.propono.atom.server.AtomServlet"); + return servlets; + } + + private HttpContext createContext() { + HttpContext context = new HttpContext(); + context.setContextPath("/rome/*"); + return context; + } + + protected void tearDown() throws Exception { + if (server != null) { + server.stop(); + server.destroy(); + server = null; + } + } +} + + diff --git a/src/test/java/com/sun/syndication/propono/atom/server/TestAtomHandlerFactory.java b/src/test/java/com/sun/syndication/propono/atom/server/TestAtomHandlerFactory.java new file mode 100644 index 0000000..ddf665e --- /dev/null +++ b/src/test/java/com/sun/syndication/propono/atom/server/TestAtomHandlerFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.sun.syndication.propono.atom.server; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class TestAtomHandlerFactory extends AtomHandlerFactory { + + public AtomHandler newAtomHandler(HttpServletRequest req, HttpServletResponse res) { + return new TestAtomHandlerImpl(req, "build/testuploaddir"); + } +} diff --git a/src/test/java/com/sun/syndication/propono/atom/server/TestAtomHandlerImpl.java b/src/test/java/com/sun/syndication/propono/atom/server/TestAtomHandlerImpl.java new file mode 100644 index 0000000..0fdec0e --- /dev/null +++ b/src/test/java/com/sun/syndication/propono/atom/server/TestAtomHandlerImpl.java @@ -0,0 +1,31 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.sun.syndication.propono.atom.server; + +import com.sun.syndication.propono.atom.server.impl.FileBasedAtomHandler; +import javax.servlet.http.HttpServletRequest; + +public class TestAtomHandlerImpl extends FileBasedAtomHandler { + + public TestAtomHandlerImpl(HttpServletRequest req, String uploaddir) { + super(req, uploaddir); + } + public boolean validateUser( String loginId, String password ) { + return AtomClientServerTest.USERNAME.equals(loginId) + && AtomClientServerTest.PASSWORD.equals(password); + } +} \ No newline at end of file diff --git a/src/test/java/com/sun/syndication/propono/blogclient/SimpleBlogClientTest.java b/src/test/java/com/sun/syndication/propono/blogclient/SimpleBlogClientTest.java new file mode 100644 index 0000000..910dcaf --- /dev/null +++ b/src/test/java/com/sun/syndication/propono/blogclient/SimpleBlogClientTest.java @@ -0,0 +1,215 @@ +/* + * Copyright 2007 Sun Microsystems, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.sun.syndication.propono.blogclient; + +import com.sun.syndication.io.impl.Atom10Parser; +import com.sun.syndication.propono.utils.Utilities; +import java.io.File; +import java.util.Iterator; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + + + +/** + * Tests Atom and MetaWeblog API CRUD via BlogClient. + * Exclude this from automated tests because it requires a live blog server. + */ +public class SimpleBlogClientTest extends TestCase { + + private String metaweblogEndpoint = "http://localhost:8080/roller/roller-services/xmlrpc"; + //private String atomEndpoint = "http://localhost:8080/roller/roller-services/app"; + private String atomEndpoint = "http://localhost:8080/sample-atomserver/app"; + + private String endpoint = "http://localhost:8080/atom-fileserver/app"; + private String username = "admin"; + private String password = "admin"; + + public SimpleBlogClientTest(String testName) { + super(testName); + } + + protected void setUp() throws Exception { + } + + protected void tearDown() throws Exception { + } + + public void testBlogClientAtom() throws Exception { + testBlogClient("atom", atomEndpoint); + } + + public void testBlogClientMetaWeblog() throws Exception{ + testBlogClient("metaweblog", metaweblogEndpoint); + } + + public void testBlogClient(String type, String endpoint) throws Exception { + BlogConnection conn = BlogConnectionFactory + .getBlogConnection(type, endpoint, username, password); + + int blogCount = 0; + for (Iterator it = conn.getBlogs().iterator(); it.hasNext();) { + Blog blog = (Blog) it.next(); + System.out.println(blog.getName()); + blogCount++; + } + assertTrue(blogCount > 0); + } + + public void testPostAndDeleteAtom() throws Exception { + testPostAndDelete("atom", atomEndpoint); + } + + public void testPostAndDeleteMetaWeblog() throws Exception { + testPostAndDelete("metaweblog", metaweblogEndpoint); + } + + public void testMediaPostAtom() throws Exception { + testMediaPost("atom", atomEndpoint); + } + + public void testMediaPostMetaWeblog() throws Exception { + testMediaPost("metaweblog", metaweblogEndpoint); + } + + public void testPostAndDelete(String type, String endpoint) throws Exception { + BlogConnection conn = BlogConnectionFactory + .getBlogConnection(type, endpoint, username, password); + assertNotNull(conn); + + String title1 = "Test content"; + String content1 = "Test content"; + + Blog blog = (Blog)conn.getBlogs().get(0); + BlogEntry entry = blog.newEntry(); + entry.setTitle(title1); + entry.setContent(new BlogEntry.Content(content1)); + entry.save(); + String token = entry.getToken(); + assertNotNull(token); + + entry = blog.getEntry(token); + + assertEquals(title1, entry.getTitle()); + assertEquals(content1, entry.getContent().getValue()); + + assertNotNull(entry); + entry.delete(); + entry = null; + + boolean notFound = false; + try { + entry = blog.getEntry(token); + } catch (Exception e) { + notFound = true; + } + assertTrue(notFound); + } + + /** + * Post media entry to every media colletion avialable on server, then cleanup. + */ + public void testMediaPost(String type, String endpoint) throws Exception { + BlogConnection conn = BlogConnectionFactory + .getBlogConnection(type, endpoint, username, password); + assertNotNull(conn); + + assertTrue(conn.getBlogs().size() > 0); + int count = 0; + for (Iterator it = conn.getBlogs().iterator(); it.hasNext();) { + Blog blog = (Blog) it.next(); + assertNotNull(blog.getName()); + + for (Iterator colit = blog.getCollections().iterator(); colit.hasNext();) { + Blog.Collection col = (Blog.Collection) colit.next(); + if (col.accepts("image/gif")) { + + // we found a collection that accepts GIF, so post one + BlogResource m1 = col.newResource("duke"+count, "image/gif", + Utilities.getBytesFromFile(new File("test/testdata/duke-wave-shadow.gif"))); + col.saveResource(m1); + + if ("atom".equals(type)) { // additional tests for Atom + + // entry should now exist on server + BlogResource m2 = (BlogResource)blog.getEntry(m1.getToken()); + assertNotNull(m2); + + // remove entry + m2.delete(); + + // fetching entry now should result in exception + boolean failed = false; + try { + blog.getEntry(m1.getToken()); + } catch (Exception e) { + failed = true; + } + assertTrue(failed); + } + count++; + } + } + } + assertTrue(count > 0); + } + + + public void testEntryIterationAtom() throws Exception { + testEntryIteration("atom", atomEndpoint); + } + + public void testEntryIterationMetaWeblog() throws Exception { + testEntryIteration("metaweblog", metaweblogEndpoint); + } + + public void testEntryIteration(String type, String endpoint) throws Exception { + BlogConnection conn = BlogConnectionFactory + .getBlogConnection(type, endpoint, username, password); + assertNotNull(conn); + + String title1 = "Test content"; + String content1 = "Test content"; + + Blog blog = (Blog)conn.getBlogs().get(0); + + for (int i=0; i<10; i++) { + BlogEntry entry = blog.newEntry(); + entry.setTitle(title1); + entry.setContent(new BlogEntry.Content(content1)); + entry.save(); + String token = entry.getToken(); + assertTrue(Atom10Parser.isAbsoluteURI(token)); + assertNotNull(token); + } + + for (Iterator it = blog.getEntries(); it.hasNext();) { + BlogEntry blogEntry = (BlogEntry)it.next(); + assertTrue(Atom10Parser.isAbsoluteURI(blogEntry.getToken())); + blogEntry.delete(); + } + } + + + public static Test suite() { + TestSuite suite = new TestSuite(SimpleBlogClientTest.class); + return suite; + } +} + + + diff --git a/src/test/resources/commons-logging.properties b/src/test/resources/commons-logging.properties new file mode 100644 index 0000000..a7bb501 --- /dev/null +++ b/src/test/resources/commons-logging.properties @@ -0,0 +1,16 @@ +# +# Copyright 2007 Sun Microsystems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog \ No newline at end of file diff --git a/src/test/resources/duke-wave-shadow.gif b/src/test/resources/duke-wave-shadow.gif new file mode 100644 index 0000000000000000000000000000000000000000..1e5ac908fc3a9e59ed5f8802839d6cb9ed07d0ee GIT binary patch literal 2700 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 literal 0 HcmV?d00001 diff --git a/src/test/resources/servicedoc1.xml b/src/test/resources/servicedoc1.xml new file mode 100644 index 0000000..57270ce --- /dev/null +++ b/src/test/resources/servicedoc1.xml @@ -0,0 +1,18 @@ + + + + adminblog1 + + Weblog Entries + + + + + entry + + + Media Files + */* + + + \ No newline at end of file diff --git a/src/test/resources/simplelog.properties b/src/test/resources/simplelog.properties new file mode 100644 index 0000000..318d7fb --- /dev/null +++ b/src/test/resources/simplelog.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. The ASF licenses this file to You +# under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. For additional information regarding +# copyright in this work, please see the NOTICE file in the top level +# directory of this distribution. + +org.apache.commons.logging.simplelog.log.com.sun.syndication.propono.atom.server.impl.FileBasedAtomHandler=debug + +org.apache.commons.logging.simplelog.log.com.sun.syndication.propono.atom.client.AtomClientTest=debug