Merge rome-propono into rome

This commit is contained in:
mishako 2016-02-13 18:50:38 +01:00
commit fdfabe4f11
107 changed files with 11178 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/.classpath
/.project
/.settings
/target

203
LICENSE Normal file
View file

@ -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.

26
NOTICE Normal file
View file

@ -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.

6
README.md Normal file
View file

@ -0,0 +1,6 @@
rome
====
ROME is a set of RSS and Atom Utilities for Java. It makes it easy to work in Java with most syndication formats: RSS 0.90, RSS 0.91 Netscape, RSS 0.91 Userland, RSS 0.92, RSS 0.93, RSS 0.94, RSS 1.0, RSS 2.0, Atom 0.3, Atom 1.0
More Information: http://rometools.github.io/rome-propono/

122
pom.xml Normal file
View file

@ -0,0 +1,122 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.rometools</groupId>
<artifactId>rome-parent</artifactId>
<version>1.6.0-SNAPSHOT</version>
</parent>
<artifactId>rome-propono</artifactId>
<version>1.6.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>rome-propono</name>
<description>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.
</description>
<url>http://rometools.github.io/rome-propono/</url>
<scm>
<connection>scm:git:ssh://github.com/rometools/rome-propono.git</connection>
<developerConnection>scm:git:ssh://git@github.com/rometools/rome-propono.git</developerConnection>
<url>https://github.com/rometools/rome-propono</url>
</scm>
<developers>
<developer>
<name>Dave Johnson</name>
<url>http://rollerweblogger.org/roller</url>
<timezone>-5</timezone>
</developer>
<developer>
<name>Robert Cooper</name>
<email>kebernet@gmail.com</email>
<url>http://www.screaming-penguin.com</url>
<timezone>-4</timezone>
</developer>
</developers>
<repositories>
<repository>
<id>sonatype-nexus-snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-scm-publish-plugin</artifactId>
<configuration>
<scmBranch>gh-pages</scmBranch>
<pubScmUrl>${project.scm.developerConnection}</pubScmUrl>
<content>${project.build.directory}/site</content>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>1.6.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.xmlrpc</groupId>
<artifactId>xmlrpc-client</artifactId>
</dependency>
<dependency>
<groupId>net.oauth.core</groupId>
<artifactId>oauth</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>jetty</groupId>
<artifactId>jetty</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,44 @@
/*
* 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.rometools.propono.atom.client;
import com.rometools.propono.utils.ProponoException;
import com.rometools.rome.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(final String uri, final AuthStrategy authStrategy) throws ProponoException {
return new ClientAtomService(uri, authStrategy);
}
/**
* Create ClientCollection bound to URI.
*/
public static ClientCollection getCollection(final String uri, final AuthStrategy authStrategy) throws ProponoException {
return new ClientCollection(uri, authStrategy);
}
}

View file

@ -0,0 +1,29 @@
/*
* 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.rometools.propono.atom.client;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import com.rometools.propono.utils.ProponoException;
public interface AuthStrategy {
/**
* Add authentication credenticals, tokens, etc. to HTTP method
*/
void addAuthentication(HttpClient httpClient, HttpMethodBase method) throws ProponoException;
}

View file

@ -0,0 +1,42 @@
/*
* 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.rometools.propono.atom.client;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import com.rometools.propono.utils.ProponoException;
import com.rometools.rome.io.impl.Base64;
public class BasicAuthStrategy implements AuthStrategy {
private final String credentials;
public BasicAuthStrategy(final String username, final String password) {
new Base64();
credentials = new String(Base64.encode((username + ":" + password).getBytes()));
}
public void init() throws ProponoException {
// op-op
}
@Override
public void addAuthentication(final HttpClient httpClient, final HttpMethodBase method) throws ProponoException {
httpClient.getParams().setAuthenticationPreemptive(true);
final String header = "Basic " + credentials;
method.setRequestHeader("Authorization", header);
}
}

View file

@ -0,0 +1,139 @@
/*
* 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.rometools.propono.atom.client;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Locale;
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.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.propono.atom.common.AtomService;
import com.rometools.propono.utils.ProponoException;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.io.impl.Atom10Parser;
/**
* This class models an Atom Publising Protocol Service Document. It extends the common
* {@link com.rometools.rome.propono.atom.common.Collection} class to add a <code>getEntry()</code>
* method and to return {@link com.rometools.rome.propono.atom.client.ClientWorkspace} objects
* instead of common {@link com.rometools.rome.propono.atom.common.Workspace}s.
*/
public class ClientAtomService extends AtomService {
private static final Logger LOG = LoggerFactory.getLogger(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(final String uri, final AuthStrategy authStrategy) throws ProponoException {
this.uri = uri;
this.authStrategy = authStrategy;
final Document doc = getAtomServiceDocument();
parseAtomServiceDocument(doc);
}
/**
* Get full entry from service by entry edit URI.
*/
public ClientEntry getEntry(final String uri) throws ProponoException {
final 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());
}
final Entry romeEntry = Atom10Parser.parseEntry(new InputStreamReader(method.getResponseBodyAsStream()), uri, Locale.US);
if (!romeEntry.isMediaEntry()) {
return new ClientEntry(this, null, romeEntry, false);
} else {
return new ClientMediaEntry(this, null, romeEntry, false);
}
} catch (final Exception e) {
throw new ProponoException("ERROR: getting or parsing entry/media", e);
} finally {
method.releaseConnection();
}
}
void addAuthentication(final HttpMethodBase method) throws ProponoException {
authStrategy.addAuthentication(httpClient, method);
}
AuthStrategy getAuthStrategy() {
return authStrategy;
}
private Document getAtomServiceDocument() throws ProponoException {
GetMethod method = null;
final 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);
final SAXBuilder builder = new SAXBuilder();
final String doc = method.getResponseBodyAsString();
LOG.debug(doc);
return builder.build(method.getResponseBodyAsStream());
} catch (final Throwable t) {
final String msg = "ERROR retrieving Atom Service Document, code: " + code;
LOG.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(final Document document) throws ProponoException {
final Element root = document.getRootElement();
final List<Element> spaces = root.getChildren("workspace", AtomService.ATOM_PROTOCOL);
for (final Element e : spaces) {
addWorkspace(new ClientWorkspace(e, this, uri));
}
}
/**
* Package access to httpClient.
*/
HttpClient getHttpClient() {
return httpClient;
}
}

View file

@ -0,0 +1,68 @@
/*
* 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.rometools.propono.atom.client;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.commons.httpclient.methods.GetMethod;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;
import com.rometools.propono.atom.common.Categories;
import com.rometools.propono.utils.ProponoException;
/**
* Models an Atom protocol Categories element, which may contain ROME Atom
* {@link com.rometools.rome.feed.atom.Category} elements.
*/
public class ClientCategories extends Categories {
private ClientCollection clientCollection = null;
/** Load select from XML element */
public ClientCategories(final Element e, final ClientCollection clientCollection) throws ProponoException {
this.clientCollection = clientCollection;
parseCategoriesElement(e);
if (getHref() != null) {
fetchContents();
}
}
public void fetchContents() throws ProponoException {
final 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());
}
final SAXBuilder builder = new SAXBuilder();
final Document catsDoc = builder.build(new InputStreamReader(method.getResponseBodyAsStream()));
parseCategoriesElement(catsDoc.getRootElement());
} catch (final IOException ioe) {
throw new ProponoException("ERROR: reading out-of-line categories", ioe);
} catch (final JDOMException jde) {
throw new ProponoException("ERROR: parsing out-of-line categories", jde);
} finally {
method.releaseConnection();
}
}
}

View file

@ -0,0 +1,225 @@
/*
* 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.rometools.propono.atom.client;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
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.jdom2.Element;
import com.rometools.propono.atom.common.AtomService;
import com.rometools.propono.atom.common.Categories;
import com.rometools.propono.atom.common.Collection;
import com.rometools.propono.atom.common.Workspace;
import com.rometools.propono.utils.ProponoException;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.io.impl.Atom10Parser;
/**
* Models an Atom collection, extends Collection and adds methods for adding, retrieving, updateing
* and deleting entries.
*/
public class ClientCollection extends Collection {
private final boolean writable = true;
private HttpClient httpClient = null;
private AuthStrategy authStrategy = null;
private ClientWorkspace workspace = null;
private ClientAtomService service = null;
ClientCollection(final Element e, final ClientWorkspace workspace, final String baseURI) throws ProponoException {
super(e, baseURI);
this.workspace = workspace;
service = workspace.getAtomService();
httpClient = workspace.getAtomService().getHttpClient();
authStrategy = workspace.getAtomService().getAuthStrategy();
parseCollectionElement(e);
}
ClientCollection(final String href, final 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 (final Throwable t) {
throw new ProponoException("ERROR creating HTTPClient", t);
}
}
void addAuthentication(final 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<ClientEntry> 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(final String uri) throws ProponoException {
final 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());
}
final Entry romeEntry = Atom10Parser.parseEntry(new InputStreamReader(method.getResponseBodyAsStream()), uri, Locale.US);
if (!romeEntry.isMediaEntry()) {
return new ClientEntry(service, this, romeEntry, false);
} else {
return new ClientMediaEntry(service, this, romeEntry, false);
}
} catch (final 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(final String title, final String slug, final String contentType, final 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(final String title, final String slug, final String contentType, final 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(final ClientEntry entry) throws ProponoException {
if (!isWritable()) {
throw new ProponoException("Collection is not writable");
}
entry.addToCollection(this);
}
@Override
protected void parseCollectionElement(final Element element) throws ProponoException {
if (workspace == null) {
return;
}
setHref(element.getAttribute("href").getValue());
final 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());
}
}
final List<Element> acceptElems = element.getChildren("accept", AtomService.ATOM_PROTOCOL);
if (acceptElems != null && !acceptElems.isEmpty()) {
for (final Element acceptElem : acceptElems) {
addAccept(acceptElem.getTextTrim());
}
}
// Loop to parse <app:categories> element to Categories objects
final List<Element> catsElems = element.getChildren("categories", AtomService.ATOM_PROTOCOL);
for (final Element catsElem : catsElems) {
final Categories cats = new ClientCategories(catsElem, this);
addCategories(cats);
}
}
}

View file

@ -0,0 +1,267 @@
/*
* 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.rometools.propono.atom.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
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.DeleteMethod;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.propono.utils.ProponoException;
import com.rometools.propono.utils.Utilities;
import com.rometools.rome.feed.atom.Content;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Link;
import com.rometools.rome.io.impl.Atom10Generator;
import com.rometools.rome.io.impl.Atom10Parser;
/**
* 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 long serialVersionUID = 1L;
private static final Logger LOG = LoggerFactory.getLogger(ClientEntry.class);
private ClientAtomService service = null;
private ClientCollection collection = null;
protected boolean partial = false;
public ClientEntry(final ClientAtomService service, final ClientCollection collection) {
this.service = service;
this.collection = collection;
}
public ClientEntry(final ClientAtomService service, final ClientCollection collection, final Entry entry, final boolean partial) throws ProponoException {
this.service = service;
this.collection = collection;
this.partial = partial;
try {
BeanUtils.copyProperties(this, entry);
} catch (final 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(final String contentString, final String type) {
final Content newContent = new Content();
newContent.setType(type == null ? Content.HTML : type);
newContent.setValue(contentString);
final ArrayList<Content> contents = new ArrayList<Content>();
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(final Content c) {
final ArrayList<Content> contents = new ArrayList<Content>();
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().isEmpty()) {
final Content c = getContents().get(0);
return c;
}
return null;
}
/**
* Determines if entries are equal based on edit URI.
*/
@Override
public boolean equals(final Object o) {
if (o instanceof ClientEntry) {
final 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
* <code>getEntry()</code> methods in {@link com.rometools.rome.propono.atom.common.Collection}
* or {@link com.rometools.rome.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");
}
final EntityEnclosingMethod method = new PutMethod(getEditURI());
addAuthentication(method);
final StringWriter sw = new StringWriter();
final int code = -1;
try {
Atom10Generator.serializeEntry(this, sw);
method.setRequestEntity(new StringRequestEntity(sw.toString(), null, null));
method.setRequestHeader("Content-type", "application/atom+xml; charset=utf-8");
getHttpClient().executeMethod(method);
final InputStream is = method.getResponseBodyAsStream();
if (method.getStatusCode() != 200 && method.getStatusCode() != 201) {
throw new ProponoException("ERROR HTTP status=" + method.getStatusCode() + " : " + Utilities.streamToString(is));
}
} catch (final Exception e) {
final String msg = "ERROR: updating entry, HTTP code: " + code;
LOG.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");
}
final DeleteMethod method = new DeleteMethod(getEditURI());
addAuthentication(method);
try {
getHttpClient().executeMethod(method);
} catch (final IOException ex) {
throw new ProponoException("ERROR: removing entry, HTTP code", ex);
} finally {
method.releaseConnection();
}
}
void setCollection(final 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 < getOtherLinks().size(); i++) {
final Link link = getOtherLinks().get(i);
if (link.getRel() != null && link.getRel().equals("edit")) {
return link.getHrefResolved();
}
}
return null;
}
void addToCollection(final ClientCollection col) throws ProponoException {
setCollection(col);
final EntityEnclosingMethod method = new PostMethod(getCollection().getHrefResolved());
addAuthentication(method);
final StringWriter sw = new StringWriter();
int code = -1;
try {
Atom10Generator.serializeEntry(this, sw);
method.setRequestEntity(new StringRequestEntity(sw.toString(), null, null));
method.setRequestHeader("Content-type", "application/atom+xml; charset=utf-8");
getHttpClient().executeMethod(method);
final InputStream is = method.getResponseBodyAsStream();
code = method.getStatusCode();
if (code != 200 && code != 201) {
throw new ProponoException("ERROR HTTP status=" + code + " : " + Utilities.streamToString(is));
}
final Entry romeEntry = Atom10Parser.parseEntry(new InputStreamReader(is), getCollection().getHrefResolved(), Locale.US);
BeanUtils.copyProperties(this, romeEntry);
} catch (final Exception e) {
final String msg = "ERROR: saving entry, HTTP code: " + code;
LOG.debug(msg, e);
throw new ProponoException(msg, e);
} finally {
method.releaseConnection();
}
final Header locationHeader = method.getResponseHeader("Location");
if (locationHeader == null) {
LOG.warn("WARNING added entry, but no location header returned");
} else if (getEditURI() == null) {
final List<Link> links = getOtherLinks();
final Link link = new Link();
link.setHref(locationHeader.getValue());
link.setRel("edit");
links.add(link);
setOtherLinks(links);
}
}
void addAuthentication(final HttpMethodBase method) throws ProponoException {
if (service != null) {
service.addAuthentication(method);
} else if (collection != null) {
collection.addAuthentication(method);
}
}
HttpClient getHttpClient() {
if (service != null) {
return service.getHttpClient();
} else if (collection != null) {
return collection.getHttpClient();
}
return null;
}
@Override
public void setCreated(final Date d) {
// protected against null created property (an old Atom 0.3 property)
if (d != null) {
super.setCreated(d);
}
}
}

View file

@ -0,0 +1,304 @@
/*
* 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.rometools.propono.atom.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.jdom2.JDOMException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.propono.utils.ProponoException;
import com.rometools.propono.utils.Utilities;
import com.rometools.rome.feed.atom.Content;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Link;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.impl.Atom10Generator;
import com.rometools.rome.io.impl.Atom10Parser;
/**
* Client implementation of Atom media-link entry, an Atom entry that provides meta-data for a media
* file (e.g. uploaded image or audio file).
*/
public class ClientMediaEntry extends ClientEntry {
private static final long serialVersionUID = 1L;
private static final Logger LOG = LoggerFactory.getLogger(ClientMediaEntry.class);
private String slug = null;
private byte[] bytes;
private InputStream inputStream;
/**
* Create ClientMedieEntry for service and collection.
*/
public ClientMediaEntry(final ClientAtomService service, final ClientCollection collection) {
super(service, collection);
}
public ClientMediaEntry(final ClientAtomService service, final ClientCollection collection, final Entry entry, final boolean partial)
throws ProponoException {
super(service, collection, entry, partial);
}
public ClientMediaEntry(final ClientAtomService service, final ClientCollection collection, final String title, final String slug,
final String contentType, final InputStream is) {
this(service, collection);
inputStream = is;
setTitle(title);
setSlug(slug);
final Content content = new Content();
content.setType(contentType);
final List<Content> contents = new ArrayList<Content>();
contents.add(content);
setContents(contents);
}
public ClientMediaEntry(final ClientAtomService service, final ClientCollection collection, final String title, final String slug,
final String contentType, final byte[] bytes) {
this(service, collection);
this.bytes = bytes;
setTitle(title);
setSlug(slug);
final Content content = new Content();
content.setType(contentType);
final List<Content> contents = new ArrayList<Content>();
contents.add(content);
setContents(contents);
}
/**
* Get bytes of media resource associated with entry.
*
* @return Bytes or null if none available or if entry uses an InputStream instead.
*/
public byte[] getBytes() {
return bytes;
}
/**
* Set media resource data as a byte array, don't try this if you have already set the data as
* an InputStream.
*/
public void setBytes(final byte[] bytes) {
if (inputStream != null) {
throw new IllegalStateException("ERROR: already has inputStream, cannot set both inputStream and bytes");
}
this.bytes = bytes;
}
/**
* Get input stream for media resource associated with this entry.
*
* @return InputStream or null if none available or if entry uses bytes instead.
*/
public InputStream getInputStream() {
return inputStream;
}
/**
* Set media resource data as an input stream, don't try this if you have already set the data
* as a byte array.
*/
public void setInputStream(final InputStream inputStream) {
if (bytes != null) {
throw new IllegalStateException("ERROR: already has bytes, cannot set both bytes and inputStream");
}
this.inputStream = inputStream;
}
/**
* Get media link URI for editing the media resource associated with this entry via HTTP PUT or
* DELETE.
*/
public String getMediaLinkURI() {
for (int i = 0; i < getOtherLinks().size(); i++) {
final Link link = getOtherLinks().get(i);
if (link.getRel() != null && link.getRel().equals("edit-media")) {
return link.getHrefResolved();
}
}
return null;
}
/**
* Get media resource as an InputStream, should work regardless of whether you set the media
* resource data as an InputStream or as a byte array.
*/
public InputStream getAsStream() throws ProponoException {
if (getContents() != null && !getContents().isEmpty()) {
final Content c = 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");
}
final 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 (final IOException e) {
throw new ProponoException("ERROR: getting media entry", e);
}
}
/**
* Update entry on server.
*/
@Override
public void update() throws ProponoException {
if (partial) {
throw new ProponoException("ERROR: attempt to update partial entry");
}
EntityEnclosingMethod method = null;
final Content updateContent = 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());
final StringWriter sw = new StringWriter();
Atom10Generator.serializeEntry(this, sw);
method.setRequestEntity(new StringRequestEntity(sw.toString(), null, null));
method.setRequestHeader("Content-type", "application/atom+xml; charset=utf8");
} else {
throw new ProponoException("ERROR: media entry has no edit URI or media-link URI");
}
getCollection().addAuthentication(method);
method.addRequestHeader("Title", getTitle());
getCollection().getHttpClient().executeMethod(method);
if (inputStream != null) {
inputStream.close();
}
final InputStream is = method.getResponseBodyAsStream();
if (method.getStatusCode() != 200 && method.getStatusCode() != 201) {
throw new ProponoException("ERROR HTTP status=" + method.getStatusCode() + " : " + Utilities.streamToString(is));
}
} catch (final 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 */
@Override
void addToCollection(final ClientCollection col) throws ProponoException {
setCollection(col);
final EntityEnclosingMethod method = new PostMethod(col.getHrefResolved());
getCollection().addAuthentication(method);
try {
final Content c = 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();
}
final InputStream is = method.getResponseBodyAsStream();
if (method.getStatusCode() == 200 || method.getStatusCode() == 201) {
final Entry romeEntry = Atom10Parser.parseEntry(new InputStreamReader(is), col.getHrefResolved(), Locale.US);
BeanUtils.copyProperties(this, romeEntry);
} else {
throw new ProponoException("ERROR HTTP status-code=" + method.getStatusCode() + " status-line: " + method.getStatusLine());
}
} catch (final IOException ie) {
throw new ProponoException("ERROR: saving media entry", ie);
} catch (final JDOMException je) {
throw new ProponoException("ERROR: saving media entry", je);
} catch (final FeedException fe) {
throw new ProponoException("ERROR: saving media entry", fe);
} catch (final IllegalAccessException ae) {
throw new ProponoException("ERROR: saving media entry", ae);
} catch (final InvocationTargetException te) {
throw new ProponoException("ERROR: saving media entry", te);
}
final Header locationHeader = method.getResponseHeader("Location");
if (locationHeader == null) {
LOG.warn("WARNING added entry, but no location header returned");
} else if (getEditURI() == null) {
final List<Link> links = getOtherLinks();
final 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(final String slug) {
this.slug = slug;
}
}

View file

@ -0,0 +1,62 @@
/*
* 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.rometools.propono.atom.client;
import java.util.List;
import org.jdom2.Element;
import com.rometools.propono.atom.common.AtomService;
import com.rometools.propono.atom.common.Workspace;
import com.rometools.propono.utils.ProponoException;
/**
* Represents Atom protocol workspace on client-side. It extends the common
* {@link com.rometools.rome.propono.atom.common.Workspace} to return
* {@link com.rometools.rome.propono.atom.client.ClientCollection} objects instead of common
* {@link com.rometools.rome.propono.atom.common.Collection}s.
*/
public class ClientWorkspace extends Workspace {
private ClientAtomService atomService = null;
ClientWorkspace(final Element e, final ClientAtomService atomService, final 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(final Element element, final String baseURI) throws ProponoException {
final 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());
}
final List<Element> collections = element.getChildren("collection", AtomService.ATOM_PROTOCOL);
for (final Element e : collections) {
addCollection(new ClientCollection(e, this, baseURI));
}
}
}

View file

@ -0,0 +1,129 @@
/*
* 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.rometools.propono.atom.client;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.jdom2.Document;
import org.jdom2.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.propono.utils.ProponoException;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Feed;
import com.rometools.rome.feed.atom.Link;
import com.rometools.rome.io.WireFeedInput;
/**
* Enables iteration over entries in Atom protocol collection.
*/
public class EntryIterator implements Iterator<ClientEntry> {
private static final Logger LOG = LoggerFactory.getLogger(EntryIterator.class);
private final ClientCollection collection;
private Iterator<Entry> members = null;
private Feed col = null;
private final String collectionURI;
private String nextURI;
EntryIterator(final ClientCollection collection) throws ProponoException {
this.collection = collection;
collectionURI = collection.getHrefResolved();
nextURI = collectionURI;
getNextEntries();
}
/**
* Returns true if more entries are available.
*/
@Override
public boolean hasNext() {
if (!members.hasNext()) {
try {
getNextEntries();
} catch (final Exception ignored) {
LOG.error("An error occured while getting next entries", ignored);
}
}
return members.hasNext();
}
/**
* Get next entry in collection.
*/
@Override
public ClientEntry next() {
if (hasNext()) {
final Entry romeEntry = members.next();
try {
if (!romeEntry.isMediaEntry()) {
return new ClientEntry(null, collection, romeEntry, true);
} else {
return new ClientMediaEntry(null, collection, romeEntry, true);
}
} catch (final ProponoException e) {
throw new RuntimeException("Unexpected exception creating ClientEntry or ClientMedia", e);
}
}
throw new NoSuchElementException();
}
/**
* Remove entry is not implemented.
*/
@Override
public void remove() {
// optional method, not implemented
}
private void getNextEntries() throws ProponoException {
if (nextURI == null) {
return;
}
final GetMethod colGet = new GetMethod(collection.getHrefResolved(nextURI));
collection.addAuthentication(colGet);
try {
collection.getHttpClient().executeMethod(colGet);
final SAXBuilder builder = new SAXBuilder();
final Document doc = builder.build(colGet.getResponseBodyAsStream());
final WireFeedInput feedInput = new WireFeedInput();
col = (Feed) feedInput.build(doc);
} catch (final 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();
col.getEntries().size();
nextURI = null;
final List<Link> altLinks = col.getOtherLinks();
if (altLinks != null) {
for (final Link link : altLinks) {
if ("next".equals(link.getRel())) {
nextURI = link.getHref();
}
}
}
}
}

View file

@ -0,0 +1,64 @@
/*
* 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.rometools.propono.atom.client;
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;
import com.rometools.propono.utils.ProponoException;
public class GDataAuthStrategy implements AuthStrategy {
private final String email;
private final String password;
private final String service;
private String authToken;
public GDataAuthStrategy(final String email, final String password, final String service) throws ProponoException {
this.email = email;
this.password = password;
this.service = service;
init();
}
private void init() throws ProponoException {
try {
final HttpClient httpClient = new HttpClient();
final PostMethod method = new PostMethod("https://www.google.com/accounts/ClientLogin");
final 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);
final String responseBody = method.getResponseBodyAsString();
final int authIndex = responseBody.indexOf("Auth=");
authToken = "GoogleLogin auth=" + responseBody.trim().substring(authIndex + 5);
} catch (final Throwable t) {
t.printStackTrace();
throw new ProponoException("ERROR obtaining Google authentication string", t);
}
}
@Override
public void addAuthentication(final HttpClient httpClient, final HttpMethodBase method) throws ProponoException {
httpClient.getParams().setAuthenticationPreemptive(true);
method.setRequestHeader("Authorization", authToken);
}
}

View file

@ -0,0 +1,33 @@
/*
* 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.rometools.propono.atom.client;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethodBase;
import com.rometools.propono.utils.ProponoException;
/**
* No authentication
*/
public class NoAuthStrategy implements AuthStrategy {
@Override
public void addAuthentication(final HttpClient httpClient, final HttpMethodBase method) throws ProponoException {
// no-op
}
}

View file

@ -0,0 +1,295 @@
/*
* 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.rometools.propono.atom.client;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
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;
import com.rometools.propono.utils.ProponoException;
/**
* 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 final String username;
private final String consumerKey;
private final String consumerSecret;
private final String keyType;
private final String reqUrl;
private final String authzUrl;
private final String accessUrl;
private final String nonce;
private final 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(final String username, final String key, final String secret, final String keyType, final String reqUrl, final String authzUrl,
final String accessUrl) throws ProponoException {
this.username = username;
this.reqUrl = reqUrl;
this.authzUrl = authzUrl;
this.accessUrl = accessUrl;
consumerKey = key;
consumerSecret = secret;
this.keyType = keyType;
nonce = UUID.randomUUID().toString();
timestamp = new Date().getTime() / 1000L;
init();
}
private void init() throws ProponoException {
callOAuthUri(reqUrl);
callOAuthUri(authzUrl);
callOAuthUri(accessUrl);
}
@Override
public void addAuthentication(final HttpClient httpClient, final 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<NameValuePair> originalqlist = null;
if (method.getQueryString() != null) {
String qstring = method.getQueryString().trim();
qstring = qstring.startsWith("?") ? qstring.substring(1) : qstring;
@SuppressWarnings("unchecked")
final List<NameValuePair> parameters = new ParameterParser().parse(qstring, '&');
originalqlist = parameters;
} else {
originalqlist = new ArrayList<NameValuePair>();
}
// put query string into hashmap form to please OAuth.net classes
final Map<String, String> params = new HashMap<String, String>();
for (final Object element : originalqlist) {
final NameValuePair pair = (NameValuePair) element;
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;
final OAuthServiceProvider provider = new OAuthServiceProvider(reqUrl, authzUrl, accessUrl);
final OAuthConsumer consumer = new OAuthConsumer(null, consumerKey, consumerSecret, provider);
final 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 (final 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(final String uri) throws ProponoException {
final HttpClient httpClient = new HttpClient();
final HttpMethodBase method;
final String content;
final Map<String, String> params = new HashMap<String, String>();
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");
final OAuthServiceProvider provider = new OAuthServiceProvider(reqUrl, authzUrl, accessUrl);
final OAuthConsumer consumer = new OAuthConsumer(null, consumerKey, consumerSecret, provider);
final OAuthAccessor accessor = new OAuthAccessor(consumer);
if (state == State.UNAUTHORIZED) {
try {
final OAuthMessage message = new OAuthMessage("GET", uri, params.entrySet());
message.sign(accessor);
final String finalUri = OAuth.addParameters(message.URL, message.getParameters());
method = new GetMethod(finalUri);
httpClient.executeMethod(method);
content = method.getResponseBodyAsString();
} catch (final 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;
final OAuthMessage message = new OAuthMessage("POST", uri, params.entrySet());
message.sign(accessor);
final String finalUri = OAuth.addParameters(message.URL, message.getParameters());
method = new PostMethod(finalUri);
httpClient.executeMethod(method);
content = method.getResponseBodyAsString();
} catch (final 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;
final OAuthMessage message = new OAuthMessage("GET", uri, params.entrySet());
message.sign(accessor);
final String finalUri = OAuth.addParameters(message.URL, message.getParameters());
method = new GetMethod(finalUri);
httpClient.executeMethod(method);
content = method.getResponseBodyAsString();
} catch (final 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) {
final String[] settings = content.split("&");
for (final String setting2 : settings) {
final String[] setting = setting2.split("=");
if (setting.length > 1) {
if ("oauth_token".equals(setting[0])) {
token = setting[1];
} else if ("oauth_token_secret".equals(setting[0])) {
secret = setting[1];
}
}
}
}
// TODO review switch without 'default'
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;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -0,0 +1,115 @@
/*
* 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.rometools.propono.atom.common;
import java.util.ArrayList;
import java.util.List;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import com.rometools.propono.utils.ProponoException;
/**
* Models an Atom Publishing Protocol Service Document. Is able to read a Service document from a
* JDOM Document and to write Service document out as a JDOM Document.
*/
public class AtomService {
private List<Workspace> workspaces = new ArrayList<Workspace>();
/** Namespace for Atom Syndication Format */
public static Namespace ATOM_FORMAT = Namespace.getNamespace("atom", "http://www.w3.org/2005/Atom");
/** Namespace for Atom Publishing Protocol */
public static Namespace ATOM_PROTOCOL = Namespace.getNamespace("app", "http://www.w3.org/2007/app");
/**
* Create new and empty Atom service
*/
public AtomService() {
}
/**
* Add Workspace to service.
*/
public void addWorkspace(final Workspace workspace) {
workspaces.add(workspace);
}
/**
* Get Workspaces available from service.
*/
public List<Workspace> getWorkspaces() {
return workspaces;
}
/**
* Set Workspaces of service.
*/
public void setWorkspaces(final List<Workspace> workspaces) {
this.workspaces = workspaces;
}
/**
* Find workspace by title.
*
* @param title Match this title
* @return Matching Workspace or null if none found.
*/
public Workspace findWorkspace(final String title) {
for (final Object element : workspaces) {
final Workspace ws = (Workspace) element;
if (title.equals(ws.getTitle())) {
return ws;
}
}
return null;
}
/**
* Deserialize an Atom service XML document into an object
*/
public static AtomService documentToService(final Document document) throws ProponoException {
final AtomService service = new AtomService();
final Element root = document.getRootElement();
final List<Element> spaces = root.getChildren("workspace", ATOM_PROTOCOL);
for (final Element e : spaces) {
service.addWorkspace(Workspace.elementToWorkspace(e));
}
return service;
}
/**
* Serialize an AtomService object into an XML document
*/
public Document serviceToDocument() {
final AtomService service = this;
final Document doc = new Document();
final Element root = new Element("service", ATOM_PROTOCOL);
doc.setRootElement(root);
final List<Workspace> spaces = service.getWorkspaces();
for (final Workspace space : spaces) {
root.addContent(space.workspaceToElement());
}
return doc;
}
}

View file

@ -0,0 +1,159 @@
/*
* 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.rometools.propono.atom.common;
import java.util.ArrayList;
import java.util.List;
import org.jdom2.Element;
import com.rometools.rome.feed.atom.Category;
import com.rometools.rome.io.impl.Atom10Parser;
/**
* Models an Atom protocol Categories element, which may contain ROME Atom
* {@link com.rometools.rome.feed.atom.Category} elements.
*/
public class Categories {
private final List<Category> categories = new ArrayList<Category>();
private String baseURI = null;
private Element categoriesElement = null;
private String href = null;
private String scheme = null;
private boolean fixed = false;
public Categories() {
}
/** Load select from XML element */
public Categories(final Element e, final String baseURI) {
categoriesElement = e;
this.baseURI = baseURI;
parseCategoriesElement(e);
}
/** Add category list of those specified */
public void addCategory(final Category cat) {
categories.add(cat);
}
/**
* Iterate over Category objects
*
* @return List of ROME Atom {@link com.rometools.rome.feed.atom.Category}
*/
public List<Category> getCategories() {
return categories;
}
/** True if clients MUST use one of the categories specified */
public boolean isFixed() {
return fixed;
}
/** True if clients MUST use one of the categories specified */
public void setFixed(final boolean fixed) {
this.fixed = fixed;
}
/** Category URI scheme to use for Categories without a scheme */
public String getScheme() {
return scheme;
}
/** Category URI scheme to use for Categories without a scheme */
public void setScheme(final String scheme) {
this.scheme = scheme;
}
/** URI of out-of-line categories */
public String getHref() {
return href;
}
/** URI of out-of-line categories */
public void setHref(final String href) {
this.href = href;
}
/** Get unresolved URI of the collection, or null if impossible to determine */
public String getHrefResolved() {
if (Atom10Parser.isAbsoluteURI(href)) {
return href;
} else if (baseURI != null && categoriesElement != null) {
return Atom10Parser.resolveURI(baseURI, categoriesElement, href);
}
return null;
}
public Element categoriesToElement() {
final Categories cats = this;
final Element catsElem = new Element("categories", AtomService.ATOM_PROTOCOL);
catsElem.setAttribute("fixed", cats.isFixed() ? "yes" : "no", AtomService.ATOM_PROTOCOL);
if (cats.getScheme() != null) {
catsElem.setAttribute("scheme", cats.getScheme(), AtomService.ATOM_PROTOCOL);
}
if (cats.getHref() != null) {
catsElem.setAttribute("href", cats.getHref(), AtomService.ATOM_PROTOCOL);
} else {
// Loop to create <atom:category> elements
for (final Object element : cats.getCategories()) {
final Category cat = (Category) element;
final 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(final 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 <atom:category> elemenents to Category objects
final List<Element> catElems = catsElem.getChildren("category", AtomService.ATOM_FORMAT);
for (final Element catElem : catElems) {
final 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);
}
}
}

View file

@ -0,0 +1,258 @@
/*
* 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.rometools.propono.atom.common;
import java.util.ArrayList;
import java.util.List;
import org.jdom2.Element;
import com.rometools.propono.utils.ProponoException;
import com.rometools.rome.io.impl.Atom10Parser;
/**
* Models an Atom workspace collection.
*/
public class Collection {
public static final String ENTRY_TYPE = "application/atom+xml;type=entry";
private final List<Categories> categories = new ArrayList<Categories>();
private Element collectionElement = null;
private String baseURI = null;
private String title = null;
private String titleType = null; // may be TEXT, HTML, XHTML
private List<String> accepts = new ArrayList<String>();
private String href = null;
/**
* 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(final String title, final String titleType, final String href) {
this.title = title;
this.titleType = titleType;
this.href = href;
}
/** Load self from XML element */
public Collection(final Element e) throws ProponoException {
collectionElement = e;
parseCollectionElement(e);
}
/** Load self from XML element and base URI for resolving relative URIs */
public Collection(final Element e, final String baseURI) throws ProponoException {
collectionElement = e;
this.baseURI = baseURI;
parseCollectionElement(e);
}
/**
* List of content-type ranges accepted by collection.
*/
public List<String> getAccepts() {
return accepts;
}
public void addAccept(final String accept) {
accepts.add(accept);
}
public void setAccepts(final List<String> accepts) {
this.accepts = accepts;
}
/** The URI of the collection */
public String getHref() {
return href;
}
/**
* Set URI of collection
*/
public void setHref(final 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) {
final 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(final String relativeUri) {
if (Atom10Parser.isAbsoluteURI(relativeUri)) {
return relativeUri;
} else if (baseURI != null && collectionElement != null) {
final 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(final 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(final String titleType) {
this.titleType = titleType;
}
/** Workspace can have multiple Categories objects */
public void addCategories(final Categories cats) {
categories.add(cats);
}
/**
* Get categories allowed by collection.
*
* @return Collection of {@link com.rometools.rome.propono.atom.common.Categories} objects.
*/
public List<Categories> getCategories() {
return categories;
}
/**
* Returns true if contentType is accepted by collection.
*/
public boolean accepts(final String ct) {
for (final Object element : accepts) {
final String accept = (String) element;
if (accept != null && accept.trim().equals("*/*")) {
return true;
}
final String entryType = "application/atom+xml";
final 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 {
final String[] rules = accepts.toArray(new String[accepts.size()]);
for (final String rule2 : rules) {
String rule = rule2.trim();
if (rule.equals(ct)) {
return true;
}
final int slashstar = rule.indexOf("/*");
if (slashstar > 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() {
final Collection collection = this;
final Element element = new Element("collection", AtomService.ATOM_PROTOCOL);
element.setAttribute("href", collection.getHref());
final 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 <app:categories> elements
for (final Object element2 : collection.getCategories()) {
final Categories cats = (Categories) element2;
element.addContent(cats.categoriesToElement());
}
for (final Object element2 : collection.getAccepts()) {
final String range = (String) element2;
final 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(final Element element) throws ProponoException {
return new Collection(element);
}
protected void parseCollectionElement(final Element element) throws ProponoException {
setHref(element.getAttribute("href").getValue());
final 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());
}
}
final List<Element> acceptElems = element.getChildren("accept", AtomService.ATOM_PROTOCOL);
if (acceptElems != null && !acceptElems.isEmpty()) {
for (final Element acceptElem : acceptElems) {
addAccept(acceptElem.getTextTrim());
}
}
// Loop to parse <app:categories> element to Categories objects
final List<Element> catsElems = element.getChildren("categories", AtomService.ATOM_PROTOCOL);
for (final Element catsElem : catsElems) {
final Categories cats = new Categories(catsElem, baseURI);
addCategories(cats);
}
}
}

View file

@ -0,0 +1,148 @@
/*
* 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.rometools.propono.atom.common;
import java.util.ArrayList;
import java.util.List;
import org.jdom2.Element;
import com.rometools.propono.utils.ProponoException;
/**
* Models an Atom workspace.
*/
public class Workspace {
private String title = null;
private String titleType = null; // may be TEXT, HTML, XHTML
private final List<Collection> collections = new ArrayList<Collection>();
/**
* Collection MUST have title.
*
* @param title Title for collection
* @param titleType Content type of title (null for plain text)
*/
public Workspace(final String title, final String titleType) {
this.title = title;
this.titleType = titleType;
}
public Workspace(final Element elem) throws ProponoException {
parseWorkspaceElement(elem);
}
/** Iterate over collections in workspace */
public List<Collection> getCollections() {
return collections;
}
/** Add new collection to workspace */
public void addCollection(final Collection col) {
collections.add(col);
}
/**
* DefaultWorkspace must have a human readable title
*/
public String getTitle() {
return title;
}
/**
* Set title of workspace.
*/
public void setTitle(final 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(final 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(final String title, final String contentType) {
for (final Object element : collections) {
final Collection col = (Collection) element;
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(final Element element) throws ProponoException {
return new Workspace(element);
}
/**
* Serialize an AtomService.DefaultWorkspace object into an XML element
*/
public Element workspaceToElement() {
final Workspace space = this;
final Element element = new Element("workspace", AtomService.ATOM_PROTOCOL);
final 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);
for (final Collection col : space.getCollections()) {
element.addContent(col.collectionToElement());
}
return element;
}
/** Deserialize a Atom workspace XML element into an object */
protected void parseWorkspaceElement(final Element element) throws ProponoException {
final 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());
}
final List<Element> collections = element.getChildren("collection", AtomService.ATOM_PROTOCOL);
for (final Element e : collections) {
addCollection(new Collection(e));
}
}
}

View file

@ -0,0 +1,44 @@
/*
* 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.rometools.propono.atom.common.rome;
import java.util.Date;
import com.rometools.rome.feed.module.Module;
/**
* 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);
}

View file

@ -0,0 +1,87 @@
/*
* 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.rometools.propono.atom.common.rome;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import org.jdom2.Element;
import org.jdom2.Namespace;
import com.rometools.rome.feed.module.Module;
import com.rometools.rome.io.ModuleGenerator;
/**
* Creates JDOM representation for APP Extension Module.
*/
public class AppModuleGenerator implements ModuleGenerator {
private static final Namespace APP_NS = Namespace.getNamespace("app", AppModule.URI);
@Override
public String getNamespaceUri() {
return AppModule.URI;
}
private static final Set<Namespace> NAMESPACES;
static {
final Set<Namespace> nss = new HashSet<Namespace>();
nss.add(APP_NS);
NAMESPACES = Collections.unmodifiableSet(nss);
}
/** Get namespaces associated with this module */
@Override
public Set<Namespace> getNamespaces() {
return NAMESPACES;
}
/** Generate JDOM element for module and add it to parent element */
@Override
public void generate(final Module module, final Element parent) {
final AppModule m = (AppModule) module;
if (m.getDraft() != null) {
final String draft = m.getDraft().booleanValue() ? "yes" : "no";
final Element control = new Element("control", APP_NS);
control.addContent(generateSimpleElement("draft", draft));
parent.addContent(control);
}
if (m.getEdited() != null) {
final Element edited = new Element("edited", APP_NS);
// Inclulde millis in date/time
final 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(final String name, final String value) {
final Element element = new Element(name, APP_NS);
element.addContent(value);
return element;
}
}

View file

@ -0,0 +1,80 @@
/*
* 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.rometools.propono.atom.common.rome;
import java.util.Date;
import com.rometools.rome.feed.CopyFrom;
import com.rometools.rome.feed.module.ModuleImpl;
/**
* Bean representation of APP module.
*/
public class AppModuleImpl extends ModuleImpl implements AppModule {
private static final long serialVersionUID = 1L;
private boolean draft = false;
private Date edited = null;
public AppModuleImpl() {
super(AppModule.class, AppModule.URI);
}
/** True if entry is draft */
@Override
public Boolean getDraft() {
return draft ? Boolean.TRUE : Boolean.FALSE;
}
/** Set to true if entry is draft */
@Override
public void setDraft(final Boolean draft) {
this.draft = draft.booleanValue();
}
/** Time of last edit */
@Override
public Date getEdited() {
return edited;
}
/** Set time of last edit */
@Override
public void setEdited(final Date edited) {
this.edited = edited;
}
/** Get interface class of module */
@Override
public Class<AppModule> getInterface() {
return AppModule.class;
}
/** Copy from other module */
@Override
public void copyFrom(final CopyFrom obj) {
final AppModule m = (AppModule) obj;
setDraft(m.getDraft());
setEdited(m.getEdited());
}
}

View file

@ -0,0 +1,72 @@
/*
* 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.rometools.propono.atom.common.rome;
import java.util.Locale;
import org.jdom2.Element;
import org.jdom2.Namespace;
import com.rometools.rome.feed.module.Module;
import com.rometools.rome.io.ModuleParser;
import com.rometools.rome.io.impl.DateParser;
/**
* Parses APP module information from a JDOM element and into <code>AppModule</code> form.
*/
public class AppModuleParser implements ModuleParser {
/** Get URI of module namespace */
@Override
public String getNamespaceUri() {
return AppModule.URI;
}
/** Get namespace of module */
public Namespace getContentNamespace() {
return Namespace.getNamespace(AppModule.URI);
}
/** Parse JDOM element into module */
@Override
public Module parse(final Element elem, final Locale locale) {
final AppModule m = new AppModuleImpl();
final Element control = elem.getChild("control", getContentNamespace());
if (control != null) {
final 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);
}
}
}
final Element edited = elem.getChild("edited", getContentNamespace());
if (edited != null) {
try {
m.setEdited(DateParser.parseW3CDateTime(edited.getTextTrim(), locale));
} catch (final Exception ignored) {
}
}
return m;
}
}

View file

@ -0,0 +1,59 @@
/*
* 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.rometools.propono.atom.server;
import javax.servlet.http.HttpServletResponse;
/**
* Exception thrown by {@link com.rometools.rome.propono.atom.server.AtomHandler} and extended by
* other Propono Atom exception classes.
*/
public class AtomException extends Exception {
private static final long serialVersionUID = 1L;
/** Construct new exception */
public AtomException() {
super();
}
/** Construct new exception with message */
public AtomException(final String msg) {
super(msg);
}
/** Contruct new exception with message and wrapping existing exception */
public AtomException(final String msg, final Throwable t) {
super(msg, t);
}
/** Construct new exception to wrap existing one. */
public AtomException(final 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;
}
}

View file

@ -0,0 +1,150 @@
/*
* 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.rometools.propono.atom.server;
import com.rometools.propono.atom.common.AtomService;
import com.rometools.propono.atom.common.Categories;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Feed;
/**
* Interface for handling single Atom protocol requests.
*
* <p>
* To create your own Atom protocol implementation you must implement this interface and create a
* concrete sub-class of {@link com.rometools.rome.propono.atom.server.AtomHandlerFactory} which is
* capable of instantiating it.
* </p>
*/
public interface AtomHandler {
/**
* Get username of authenticated user. Return the username of the authenticated user
*/
public String getAuthenticatedUsername();
/**
* Return {@link com.rometools.rome.propono.atom.common.AtomService} object that contains the
* {@link com.rometools.rome.propono.atom.common.Workspace} objects available to the currently
* authenticated user and within those the
* {@link com.rometools.rome.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);
}

View file

@ -0,0 +1,100 @@
/*
* 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.rometools.propono.atom.server;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Defines a factory that enables the {@link com.rometools.rome.propono.atom.server.AtomServlet} to
* obtain an {@link com.rometools.rome.propono.atom.server.AtomHandler} that handles an Atom
* request.
*
* <p>
* 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.rometools.rome.propono.atom.server.AtomHandler} impementation.
* </p>
*/
public abstract class AtomHandlerFactory {
private static final Logger LOG = LoggerFactory.getLogger(AtomHandlerFactory.class);
private static final String DEFAULT_PROPERTY_NAME = "com.rometools.propono.atom.server.AtomHandlerFactory";
private static final String FALLBACK_IMPL_NAME = "com.rometools.propono.atom.server.impl.FileBasedAtomHandlerFactory";
/*
* <p>Protected constructor to prevent instantiation. Use {@link #newInstance()}.</p>
*/
protected AtomHandlerFactory() {
}
/**
* Obtain a new instance of a <code>AtomHandlerFactory</code>. This static method creates a new
* factory instance. This method uses the following ordered lookup procedure to determine the
* <code>AtomHandlerFactory</code> implementation class to load:
* <ul>
* <li>
* Use the <code>com.rometools.rome.propono.atom.server.AtomHandlerFactory</code> system
* property.</li>
* <li>
* Use the properties file "/propono.properties" in the classpath. This configuration file is in
* standard <code>java.util.Properties</code> 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.</li>
* <li>
* If not available, to determine the classname. The Services API will look for a classname in
* the file: <code>META-INF/services/com.rometools.rome.AtomHandlerFactory</code> in jars
* available to the runtime.</li>
* <li>
* Platform default <code>AtomHandlerFactory</code> instance.</li>
* </ul>
*
* Once an application has obtained a reference to a <code>AtomHandlerFactory</code> it can use
* the factory to configure and obtain parser instances.
*
* @return New instance of a <code>AtomHandlerFactory</code>
*
* @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 (final ConfigurationError e) {
LOG.error("An error occured while finding factory", e);
throw new FactoryConfigurationError(e.getException(), e.getMessage());
}
}
/**
* Creates a new instance of a {@link com.rometools.rome.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);
}

View file

@ -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.rometools.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 (final Exception ignored) {
}
}
}
public AtomMediaResource(final File resource) throws FileNotFoundException {
contentType = map.getContentType(resource.getName());
contentLength = resource.length();
lastModified = new Date(resource.lastModified());
inputStream = new FileInputStream(resource);
}
public AtomMediaResource(final String name, final long length, final Date lastModified, final InputStream is) throws FileNotFoundException {
contentType = map.getContentType(name);
contentLength = length;
this.lastModified = lastModified;
inputStream = is;
}
public String getContentType() {
return contentType;
}
public void setContentType(final String contentType) {
this.contentType = contentType;
}
public long getContentLength() {
return contentLength;
}
public void setContentLength(final long contentLength) {
this.contentLength = contentLength;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(final InputStream inputStream) {
this.inputStream = inputStream;
}
public Date getLastModified() {
return lastModified;
}
public void setLastModified(final Date lastModified) {
this.lastModified = lastModified;
}
}

View file

@ -0,0 +1,58 @@
/*
* 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.rometools.propono.atom.server;
import javax.servlet.http.HttpServletResponse;
/**
* Exception to be thrown by <code>AtomHandler</code> implementations in the case that a user is not
* authorized to access a resource.
*/
public class AtomNotAuthorizedException extends AtomException {
private static final long serialVersionUID = 1L;
/** Construct new exception */
public AtomNotAuthorizedException() {
super();
}
/** Construct new exception with message */
public AtomNotAuthorizedException(final String msg) {
super(msg);
}
/** Construct new exception with message and root cause */
public AtomNotAuthorizedException(final String msg, final Throwable t) {
super(msg, t);
}
/** Construct new exception to wrap root cause */
public AtomNotAuthorizedException(final Throwable t) {
super(t);
}
/** Get HTTP status code of exception (HTTP 403 unauthorized) */
@Override
public int getStatus() {
return HttpServletResponse.SC_UNAUTHORIZED;
}
}

View file

@ -0,0 +1,56 @@
/*
* 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.rometools.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 {
private static final long serialVersionUID = 1L;
/** Construct new exception */
public AtomNotFoundException() {
super();
}
/** Construct new exception with message */
public AtomNotFoundException(final String msg) {
super(msg);
}
/** Construct new exception with message and root cause */
public AtomNotFoundException(final String msg, final Throwable t) {
super(msg, t);
}
/** Construct new exception with root cause */
public AtomNotFoundException(final Throwable t) {
super(t);
}
/** Get HTTP status code associated with exception (404 not found) */
@Override
public int getStatus() {
return HttpServletResponse.SC_NOT_FOUND;
}
}

View file

@ -0,0 +1,137 @@
/*
* 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.rometools.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<String> 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<String, Object> 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<String> getHeaders(String arg0);
/**
* Returns an enumeration of all the header names this request contains.
*/
public Enumeration<String> getHeaderNames();
/**
* Returns the value of the specified request header as an int.
*/
public int getIntHeader(String arg0);
}

View file

@ -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.rometools.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(final HttpServletRequest wrapped) {
this.wrapped = wrapped;
}
@Override
public String getPathInfo() {
return wrapped.getPathInfo() != null ? wrapped.getPathInfo() : "";
}
@Override
public String getQueryString() {
return wrapped.getQueryString();
}
@Override
public String getRemoteUser() {
return wrapped.getRemoteUser();
}
@Override
public boolean isUserInRole(final String arg0) {
return wrapped.isUserInRole(arg0);
}
@Override
public Principal getUserPrincipal() {
return wrapped.getUserPrincipal();
}
@Override
public String getRequestURI() {
return wrapped.getRequestURI();
}
@Override
public StringBuffer getRequestURL() {
return wrapped.getRequestURL();
}
@Override
public int getContentLength() {
return wrapped.getContentLength();
}
@Override
public String getContentType() {
return wrapped.getContentType();
}
@Override
public String getParameter(final String arg0) {
return wrapped.getParameter(arg0);
}
@Override
@SuppressWarnings("unchecked")
public Enumeration<String> getParameterNames() {
return wrapped.getParameterNames();
}
@Override
public String[] getParameterValues(final String arg0) {
return wrapped.getParameterValues(arg0);
}
@Override
@SuppressWarnings("unchecked")
public Map<String, Object> getParameterMap() {
return wrapped.getParameterMap();
}
@Override
public InputStream getInputStream() throws IOException {
return wrapped.getInputStream();
}
@Override
public long getDateHeader(final String arg0) {
return wrapped.getDateHeader(arg0);
}
@Override
public String getHeader(final String arg0) {
return wrapped.getHeader(arg0);
}
@Override
@SuppressWarnings("unchecked")
public Enumeration<String> getHeaders(final String arg0) {
return wrapped.getHeaders(arg0);
}
@Override
@SuppressWarnings("unchecked")
public Enumeration<String> getHeaderNames() {
return wrapped.getHeaderNames();
}
@Override
public int getIntHeader(final String arg0) {
return wrapped.getIntHeader(arg0);
}
}

View file

@ -0,0 +1,371 @@
/*
* 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.rometools.propono.atom.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.util.Collections;
import java.util.Locale;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jdom2.Document;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.propono.atom.common.AtomService;
import com.rometools.propono.atom.common.Categories;
import com.rometools.propono.utils.Utilities;
import com.rometools.rome.feed.atom.Content;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Feed;
import com.rometools.rome.feed.atom.Link;
import com.rometools.rome.io.WireFeedOutput;
import com.rometools.rome.io.impl.Atom10Generator;
import com.rometools.rome.io.impl.Atom10Parser;
/**
* Atom Servlet implements Atom protocol by calling an
* {@link com.rometools.rome.propono.atom.server.AtomHandler} implementation. This servlet takes
* care of parsing incoming XML into ROME Atom {@link com.rometools.rome.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 {
private static final long serialVersionUID = 1L;
/**
* 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 final Logger LOG = LoggerFactory.getLogger(AtomServlet.class);
static {
Atom10Parser.setResolveURIs(true);
}
// -----------------------------------------------------------------------------
/**
* Create an Atom request handler. TODO: make AtomRequestHandler implementation configurable.
*/
private AtomHandler createAtomRequestHandler(final HttpServletRequest request, final HttpServletResponse response) throws ServletException {
final AtomHandlerFactory ahf = AtomHandlerFactory.newInstance();
return ahf.newAtomHandler(request, response);
}
// -----------------------------------------------------------------------------
/**
* Handles an Atom GET by calling handler and writing results to response.
*/
@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException {
LOG.debug("Entering");
final AtomHandler handler = createAtomRequestHandler(req, res);
final String userName = handler.getAuthenticatedUsername();
if (userName != null) {
final AtomRequest areq = new AtomRequestImpl(req);
try {
if (handler.isAtomServiceURI(areq)) {
// return an Atom Service document
final AtomService service = handler.getAtomService(areq);
final Document doc = service.serviceToDocument();
res.setContentType("application/atomsvc+xml; charset=utf-8");
final Writer writer = res.getWriter();
final XMLOutputter outputter = new XMLOutputter();
outputter.setFormat(Format.getPrettyFormat());
outputter.output(doc, writer);
writer.close();
res.setStatus(HttpServletResponse.SC_OK);
} else if (handler.isCategoriesURI(areq)) {
final Categories cats = handler.getCategories(areq);
res.setContentType("application/xml");
final Writer writer = res.getWriter();
final Document catsDoc = new Document();
catsDoc.setRootElement(cats.categoriesToElement());
final XMLOutputter outputter = new XMLOutputter();
outputter.output(catsDoc, writer);
writer.close();
res.setStatus(HttpServletResponse.SC_OK);
} else if (handler.isCollectionURI(areq)) {
// return a collection
final Feed col = handler.getCollection(areq);
col.setFeedType(FEED_TYPE);
final WireFeedOutput wireFeedOutput = new WireFeedOutput();
final Document feedDoc = wireFeedOutput.outputJDom(col);
res.setContentType("application/atom+xml; charset=utf-8");
final Writer writer = res.getWriter();
final 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
final Entry entry = handler.getEntry(areq);
if (entry != null) {
res.setContentType("application/atom+xml; type=entry; charset=utf-8");
final Writer writer = res.getWriter();
Atom10Generator.serializeEntry(entry, writer);
writer.close();
} else {
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
} else if (handler.isMediaEditURI(areq)) {
final 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 (final AtomException ae) {
res.sendError(ae.getStatus(), ae.getMessage());
LOG.debug("An error occured while processing GET", ae);
} catch (final Exception e) {
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
LOG.debug("An error occured while 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.
*/
@Override
protected void doPost(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException {
LOG.debug("Entering");
final AtomHandler handler = createAtomRequestHandler(req, res);
final String userName = handler.getAuthenticatedUsername();
if (userName != null) {
final AtomRequest areq = new AtomRequestImpl(req);
try {
if (handler.isCollectionURI(areq)) {
if (req.getContentType().startsWith("application/atom+xml")) {
// parse incoming entry
final Entry entry = Atom10Parser.parseEntry(new BufferedReader(new InputStreamReader(req.getInputStream(), "UTF-8")), null, Locale.US);
// call handler to post it
final Entry newEntry = handler.postEntry(areq, entry);
// set Location and Content-Location headers
for (final Object element : newEntry.getOtherLinks()) {
final Link link = (Link) element;
if ("edit".equals(link.getRel())) {
res.addHeader("Location", link.getHrefResolved());
break;
}
}
for (final Object element : newEntry.getAlternateLinks()) {
final Link link = (Link) element;
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");
final Writer writer = res.getWriter();
Atom10Generator.serializeEntry(newEntry, writer);
writer.close();
} else if (req.getContentType() != null) {
// get incoming title and slug from HTTP header
final String title = areq.getHeader("Title");
// create new entry for resource, set title and type
final Entry resource = new Entry();
resource.setTitle(title);
final Content content = new Content();
content.setType(areq.getContentType());
resource.setContents(Collections.singletonList(content));
// hand input stream off to hander to post file
final Entry newEntry = handler.postMedia(areq, resource);
// set Location and Content-Location headers
for (final Object element : newEntry.getOtherLinks()) {
final Link link = (Link) element;
if ("edit".equals(link.getRel())) {
res.addHeader("Location", link.getHrefResolved());
break;
}
}
for (final Object element : newEntry.getAlternateLinks()) {
final Link link = (Link) element;
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");
final 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 (final AtomException ae) {
res.sendError(ae.getStatus(), ae.getMessage());
LOG.debug("An error occured while processing POST", ae);
} catch (final Exception e) {
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
LOG.debug("An error occured while 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.
*/
@Override
protected void doPut(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException {
LOG.debug("Entering");
final AtomHandler handler = createAtomRequestHandler(req, res);
final String userName = handler.getAuthenticatedUsername();
if (userName != null) {
final AtomRequest areq = new AtomRequestImpl(req);
try {
if (handler.isEntryURI(areq)) {
// parse incoming entry
final Entry unsavedEntry = Atom10Parser.parseEntry(new BufferedReader(new InputStreamReader(req.getInputStream(), "UTF-8")), null,
Locale.US);
// 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 (final AtomException ae) {
res.sendError(ae.getStatus(), ae.getMessage());
LOG.debug("An error occured while processing PUT", ae);
} catch (final Exception e) {
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
LOG.debug("An error occured while 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.
*/
@Override
protected void doDelete(final HttpServletRequest req, final HttpServletResponse res) throws ServletException, IOException {
LOG.debug("Entering");
final AtomHandler handler = createAtomRequestHandler(req, res);
final String userName = handler.getAuthenticatedUsername();
if (userName != null) {
final 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 (final AtomException ae) {
res.sendError(ae.getStatus(), ae.getMessage());
LOG.debug("An error occured while processing DELETE", ae);
} catch (final Exception e) {
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
LOG.debug("An error occured while 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.
*/
@Override
public void init(final ServletConfig config) throws ServletException {
super.init(config);
contextDirPath = getServletContext().getRealPath("/");
}
/**
* Get absolute path to Servlet context directory.
*/
public static String getContextDirPath() {
return contextDirPath;
}
}

View file

@ -0,0 +1,21 @@
package com.rometools.propono.atom.server;
class ConfigurationError extends Error {
private static final long serialVersionUID = 1L;
private final Exception exception;
/**
* Construct a new instance with the specified detail string and exception.
*/
ConfigurationError(final String msg, final Exception x) {
super(msg);
exception = x;
}
Exception getException() {
return exception;
}
}

View file

@ -0,0 +1,101 @@
/*
* 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.rometools.propono.atom.server;
/**
* Thrown when a problem with configuration with the
* {@link com.rometools.rome.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 {
private static final long serialVersionUID = 1L;
/**
* <code>Exception</code> that represents the error.
*/
private final Exception exception;
/**
* Create a new <code>FactoryConfigurationError</code> with no detail mesage.
*/
public FactoryConfigurationError() {
super();
exception = null;
}
/**
* Create a new <code>FactoryConfigurationError</code> with the <code>String </code> specified
* as an error message.
*
* @param msg The error message for the exception.
*/
public FactoryConfigurationError(final String msg) {
super(msg);
exception = null;
}
/**
* Create a new <code>FactoryConfigurationError</code> with a given <code>Exception</code> base
* cause of the error.
*
* @param e The exception to be encapsulated in a FactoryConfigurationError.
*/
public FactoryConfigurationError(final Exception e) {
super(e.toString());
exception = e;
}
/**
* Create a new <code>FactoryConfigurationError</code> with the given <code>Exception</code>
* base cause and detail message.
*
* @param e The exception to be encapsulated in a FactoryConfigurationError
* @param msg The detail message.
*/
public FactoryConfigurationError(final Exception e, final String msg) {
super(msg);
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.
*/
@Override
public String getMessage() {
final 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;
}
}

View file

@ -0,0 +1,248 @@
/*
* 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.rometools.propono.atom.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
/**
* Find {@link com.rometools.rome.propono.atom.server.AtomHandlerFactory} based on properties
* files.
*/
class FactoryFinder {
private static boolean debug = false;
private static Properties cacheProps = new Properties();
private static SecuritySupport ss = new SecuritySupport();
private static boolean firstTime = true;
private static void dPrint(final 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(final String className, ClassLoader cl, final 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 (final ClassNotFoundException x) {
if (doFallback) {
// Fall back to current classloader
cl = FactoryFinder.class.getClassLoader();
providerClass = cl.loadClass(className);
} else {
throw x;
}
}
}
final Object instance = providerClass.newInstance();
dPrint("created new instance of " + providerClass + " using ClassLoader: " + cl);
return instance;
} catch (final ClassNotFoundException x) {
throw new ConfigurationError("Provider " + className + " not found", x);
} catch (final 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(final String factoryId, final 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 {
final String systemProp = ss.getSystemProperty(factoryId);
if (systemProp != null) {
dPrint("found system property, value=" + systemProp);
return newInstance(systemProp, classLoader, true);
}
} catch (final 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 {
final String configFile = "/propono.properties";
String factoryClassName = null;
if (firstTime) {
synchronized (cacheProps) {
if (firstTime) {
try {
final InputStream is = FactoryFinder.class.getResourceAsStream(configFile);
firstTime = false;
if (is != null) {
dPrint("Read properties file: " + configFile);
cacheProps.load(is);
}
} catch (final Exception intentionallyIgnored) {
}
}
}
}
factoryClassName = cacheProps.getProperty(factoryId);
if (factoryClassName != null) {
dPrint("found in $java.home/propono.properties, value=" + factoryClassName);
return newInstance(factoryClassName, classLoader, true);
}
} catch (final Exception ex) {
if (debug) {
ex.printStackTrace();
}
}
// Try Jar Service Provider Mechanism
final 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(final String factoryId) throws ConfigurationError {
final 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 (final 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 (final 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;
}
}

View file

@ -0,0 +1,98 @@
/*
* 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.rometools.propono.atom.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
/**
* 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() {
final PrivilegedAction<ClassLoader> action = new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
} catch (final SecurityException ex) {
}
return cl;
}
};
return AccessController.doPrivileged(action);
}
String getSystemProperty(final String propName) {
final PrivilegedAction<String> action = new PrivilegedAction<String>() {
@Override
public String run() {
return System.getProperty(propName);
}
};
return AccessController.doPrivileged(action);
}
FileInputStream getFileInputStream(final File file) throws FileNotFoundException {
try {
final PrivilegedExceptionAction<FileInputStream> action = new PrivilegedExceptionAction<FileInputStream>() {
@Override
public FileInputStream run() throws FileNotFoundException {
return new FileInputStream(file);
}
};
return AccessController.doPrivileged(action);
} catch (final PrivilegedActionException e) {
throw (FileNotFoundException) e.getException();
}
}
InputStream getResourceAsStream(final ClassLoader cl, final String name) {
final PrivilegedAction<InputStream> action = new PrivilegedAction<InputStream>() {
@Override
public InputStream run() {
InputStream ris;
if (cl == null) {
ris = ClassLoader.getSystemResourceAsStream(name);
} else {
ris = cl.getResourceAsStream(name);
}
return ris;
}
};
return AccessController.doPrivileged(action);
}
boolean doesFileExist(final File f) {
final PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return f.exists();
}
};
return AccessController.doPrivileged(action).booleanValue();
}
}

View file

@ -0,0 +1,469 @@
/*
* 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.rometools.propono.atom.server.impl;
import java.io.File;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.propono.atom.common.AtomService;
import com.rometools.propono.atom.common.Categories;
import com.rometools.propono.atom.server.AtomException;
import com.rometools.propono.atom.server.AtomHandler;
import com.rometools.propono.atom.server.AtomMediaResource;
import com.rometools.propono.atom.server.AtomRequest;
import com.rometools.propono.atom.server.AtomServlet;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Feed;
/**
* File-based {@link com.rometools.rome.propono.atom.server.AtomHandler} implementation that stores
* entries and media-entries to disk. Implemented using
* {@link com.rometools.rome.propono.atom.server.impl.FileBasedAtomService}.
*/
public class FileBasedAtomHandler implements AtomHandler {
private static final Logger LOG = LoggerFactory.getLogger(FileBasedAtomHandler.class);
private String userName = null;
private String atomProtocolURL = null;
private String contextURI = null;
private FileBasedAtomService service = null;
/**
* Construct handler to handle one request.
*
* @param req Request to be handled.
*/
public FileBasedAtomHandler(final 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(final HttpServletRequest req, final 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 (final 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(final String login, final String password) {
return true;
}
/**
* Get username of authenticated user
*
* @return User name.
*/
@Override
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.rometools.rome.propono.atom.server.AtomException Unexpected exception.
* @return AtomService object with workspaces and collections.
*/
@Override
public AtomService getAtomService(final AtomRequest areq) throws AtomException {
return service;
}
/**
* Returns null because we use in-line categories.
*
* @throws com.rometools.rome.propono.atom.server.AtomException Unexpected exception.
* @return Categories object
*/
@Override
public Categories getCategories(final AtomRequest areq) throws AtomException {
LOG.debug("getCollection");
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
final String handle = pathInfo[0];
final String collection = pathInfo[1];
final FileBasedCollection col = service.findCollectionByHandle(handle, collection);
return col.getCategories(true).get(0);
}
/**
* Get collection specified by pathinfo.
*
* @param areq Details of HTTP request
* @return ROME feed representing collection.
* @throws com.rometools.rome.propono.atom.server.AtomException Invalid collection or other
* exception.
*/
@Override
public Feed getCollection(final AtomRequest areq) throws AtomException {
LOG.debug("getCollection");
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
final String handle = pathInfo[0];
final String collection = pathInfo[1];
final 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.rometools.rome.propono.atom.server.AtomException On invalid collection or other
* error.
* @return Entry as represented on server.
*/
@Override
public Entry postEntry(final AtomRequest areq, final Entry entry) throws AtomException {
LOG.debug("postEntry");
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
final String handle = pathInfo[0];
final String collection = pathInfo[1];
final FileBasedCollection col = service.findCollectionByHandle(handle, collection);
try {
return col.addEntry(entry);
} catch (final Exception fe) {
fe.printStackTrace();
throw new AtomException(fe);
}
}
/**
* Get entry specified by pathInfo.
*
* @param areq Details of HTTP request
* @throws com.rometools.rome.propono.atom.server.AtomException On invalid pathinfo or other
* error.
* @return ROME Entry object.
*/
@Override
public Entry getEntry(final AtomRequest areq) throws AtomException {
LOG.debug("getEntry");
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
final String handle = pathInfo[0];
final String collection = pathInfo[1];
final String fileName = pathInfo[2];
final FileBasedCollection col = service.findCollectionByHandle(handle, collection);
try {
return col.getEntry(fileName);
} catch (final 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.rometools.rome.propono.atom.server.AtomException
*/
@Override
public void putEntry(final AtomRequest areq, final Entry entry) throws AtomException {
LOG.debug("putEntry");
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
final String handle = pathInfo[0];
final String collection = pathInfo[1];
final String fileName = pathInfo[2];
final FileBasedCollection col = service.findCollectionByHandle(handle, collection);
try {
col.updateEntry(entry, fileName);
} catch (final Exception fe) {
throw new AtomException(fe);
}
}
/**
* Delete entry specified by pathInfo.
*
* @param areq Details of HTTP request
*/
@Override
public void deleteEntry(final AtomRequest areq) throws AtomException {
LOG.debug("deleteEntry");
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
final String handle = pathInfo[0];
final String collection = pathInfo[1];
final String fileName = pathInfo[2];
final FileBasedCollection col = service.findCollectionByHandle(handle, collection);
try {
col.deleteEntry(fileName);
} catch (final Exception e) {
final 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
*/
@Override
public Entry postMedia(final AtomRequest areq, final Entry entry) throws AtomException {
// get incoming slug from HTTP header
final String slug = areq.getHeader("Slug");
if (LOG.isDebugEnabled()) {
LOG.debug("postMedia - title: " + entry.getTitle() + " slug:" + slug);
}
try {
final File tempFile = null;
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
final String handle = pathInfo[0];
final String collection = pathInfo[1];
final FileBasedCollection col = service.findCollectionByHandle(handle, collection);
try {
col.addMediaEntry(entry, slug, areq.getInputStream());
} catch (final Exception e) {
e.printStackTrace();
final String msg = "ERROR reading posted file";
LOG.error(msg, e);
throw new AtomException(msg, e);
} finally {
if (tempFile != null) {
tempFile.delete();
}
}
} catch (final 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
*/
@Override
public void putMedia(final AtomRequest areq) throws AtomException {
LOG.debug("putMedia");
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
final String handle = pathInfo[0];
final String collection = pathInfo[1];
final String fileName = pathInfo[3];
final FileBasedCollection col = service.findCollectionByHandle(handle, collection);
try {
col.updateMediaEntry(fileName, areq.getContentType(), areq.getInputStream());
} catch (final Exception re) {
throw new AtomException("ERROR: posting media");
}
}
@Override
public AtomMediaResource getMediaResource(final AtomRequest areq) throws AtomException {
LOG.debug("putMedia");
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
final String handle = pathInfo[0];
final String collection = pathInfo[1];
final String fileName = pathInfo[3];
final FileBasedCollection col = service.findCollectionByHandle(handle, collection);
try {
return col.getMediaResource(fileName);
} catch (final Exception re) {
throw new AtomException("ERROR: posting media");
}
}
/**
* Return true if specified pathinfo represents URI of service doc.
*/
@Override
public boolean isAtomServiceURI(final AtomRequest areq) {
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
if (pathInfo.length == 0) {
return true;
}
return false;
}
/**
* Return true if specified pathinfo represents URI of category doc.
*/
@Override
public boolean isCategoriesURI(final AtomRequest areq) {
LOG.debug("isCategoriesDocumentURI");
final 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.
*/
@Override
public boolean isCollectionURI(final AtomRequest areq) {
LOG.debug("isCollectionURI");
// workspace/collection-plural
// if length is 2 and points to a valid collection then YES
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
if (pathInfo.length == 2) {
final String handle = pathInfo[0];
final 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.
*/
@Override
public boolean isEntryURI(final AtomRequest areq) {
LOG.debug("isEntryURI");
// workspace/collection-singular/fsid
// if length is 3 and points to a valid collection then YES
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
if (pathInfo.length == 3) {
final String handle = pathInfo[0];
final String collection = pathInfo[1];
if (service.findCollectionByHandle(handle, collection) != null) {
return true;
}
}
return false;
}
/**
* Return true if specified pathinfo represents media-edit URI.
*/
@Override
public boolean isMediaEditURI(final 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
final String[] pathInfo = StringUtils.split(areq.getPathInfo(), "/");
if (pathInfo.length == 4) {
final String handle = pathInfo[0];
final String collection = pathInfo[1];
final String media = pathInfo[2];
// final String fsid = pathInfo[3];
if (service.findCollectionByHandle(handle, collection) != null && media.equals("media")) {
return true;
}
}
return false;
}
/**
* BASIC authentication.
*/
public String authenticateBASIC(final HttpServletRequest request) {
LOG.debug("authenticateBASIC");
boolean valid = false;
String userID = null;
String password = null;
try {
final String authHeader = request.getHeader("Authorization");
if (authHeader != null) {
final StringTokenizer st = new StringTokenizer(authHeader);
if (st.hasMoreTokens()) {
final String basic = st.nextToken();
if (basic.equalsIgnoreCase("Basic")) {
final String credentials = st.nextToken();
final String userPass = new String(Base64.decodeBase64(credentials.getBytes()));
final 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 (final Exception e) {
LOG.debug("An error occured while processing Basic authentication", e);
}
if (valid) {
// For now assume userID as userName
return userID;
}
return null;
}
}

View file

@ -0,0 +1,38 @@
/*
* 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.rometools.propono.atom.server.impl;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.rometools.propono.atom.server.AtomHandler;
import com.rometools.propono.atom.server.AtomHandlerFactory;
/**
* Extends {@link com.rometools.rome.propono.atom.server.AtomHandlerFactory} to create and return
* {@link com.rometools.rome.propono.atom.server.impl.FileBasedAtomHandler}.
*/
public class FileBasedAtomHandlerFactory extends AtomHandlerFactory {
/**
* Create new AtomHandler.
*/
@Override
public AtomHandler newAtomHandler(final HttpServletRequest req, final HttpServletResponse res) {
return new FileBasedAtomHandler(req);
}
}

View file

@ -0,0 +1,193 @@
/*
* 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.rometools.propono.atom.server.impl;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import com.rometools.propono.atom.common.AtomService;
import com.rometools.propono.utils.Utilities;
/**
* File based Atom service. Supports one workspace per user. Collections in workspace are defined in
* /propono.properties, for example:
*
* <pre>
* # 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
* </pre>
*
* If no such properties are found, then service will fall back to two collections: 'entries' for
* Atom entries and 'resources' for any content-type.
*
*
* <p>
* <b>URI structure used for accessing collections and entries</b>
* </p>
*
* <p>
* Collection feed (URI allows GET to get collection, POST to add to it)<br/>
* <code>[servlet-context-uri]/app/[workspace-handle]/[collection-plural] </code>
* </p>
*
* <p>
* Collection entry (URI allows GET, PUT and DELETE)<br/>
* <code>[servlet-context-uri]/app/[workspace-handle]/[collection-singular]/[entryid] </code>
* </p>
*
* <p>
* Collection entry media (URI allows GET, PUT and DELETE)<br/>
* <code>[servlet-context-uri]/app/[workspace-handle]/[collection-singular]/media/[entryid]</code>
* </p>
*
* <p>
* Categories URI if not using inline categories (URI allows GET)<br/>
* <code>[servlet-context-uri]/app/[workspace-handle]/[collection-plural]/categories</code>
* </p>
*
*
* <p>
* <b>Directory structure used to store collections and entries</b>
* </p>
*
* <p>
* Collection feed (kept constantly up to date)<br/>
* <code>[servlet-context-dir]/[workspace-handle]/[collection-plural]/feed.xml</code>
* </p>
*
* <p>
* Collection entry (individual entries also stored as entry.xml files)<br/>
* <code>[servlet-context-dir]/[workspace-handle]/[collection-plural]/id/entry.xml</code>
* </p>
*
* <p>
* Collection entry media (media file stored under entry directory)<br/>
* <code>[servlet-context-dir]/[workspace-handle]/[collection-plural]/id/media/id</code>
* </p>
*/
public class FileBasedAtomService extends AtomService {
private final Map<String, FileBasedWorkspace> workspaceMap = new TreeMap<String, FileBasedWorkspace>();
private final Map<String, FileBasedCollection> collectionMap = new TreeMap<String, FileBasedCollection>();
private static Properties cacheProps = new Properties();
private boolean firstTime = true;
/**
* Creates a new instance of FileBasedAtomService.
*/
public FileBasedAtomService(final String userName, final String baseDir, final String contextURI, final String contextPath, final String servletPath)
throws Exception {
final String workspaceHandle = userName;
// One workspace per user
final FileBasedWorkspace workspace = new FileBasedWorkspace(workspaceHandle, baseDir);
workspaceMap.put(userName, workspace);
if (firstTime) {
synchronized (cacheProps) {
final 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();
}
final String relativeURIsString = cacheProps.getProperty("propono.atomserver.filebased.relativeURIs");
final boolean relativeURIs = "true".equals(relativeURIsString);
final String inlineCategoriesString = cacheProps.getProperty("propono.atomserver.filebased.inlineCategories");
final boolean inlineCategories = "true".equals(inlineCategoriesString);
final String colnames = cacheProps.getProperty("propono.atomserver.filebased.collections");
if (colnames != null) {
// collections specified in propono.properties, use those
final String[] colarray = Utilities.stringToStringArray(colnames, ",");
for (final String element : colarray) {
final String prefix = "propono.atomserver.filebased.collection." + element + ".";
final String collectionTitle = cacheProps.getProperty(prefix + "title");
final String collectionSingular = cacheProps.getProperty(prefix + "singular");
final String collectionPlural = cacheProps.getProperty(prefix + "plural");
final String collectionAccept = cacheProps.getProperty(prefix + "accept");
final String catNamesString = cacheProps.getProperty(prefix + "categories");
final String[] catNames = Utilities.stringToStringArray(catNamesString, ",");
final FileBasedCollection entries = new FileBasedCollection(collectionTitle, workspaceHandle, collectionPlural, collectionSingular,
collectionAccept, inlineCategories, catNames, relativeURIs, contextURI, contextPath, servletPath, baseDir);
workspace.addCollection(entries);
// want to be able to look up collection by singular and plural names
collectionMap.put(workspaceHandle + "|" + collectionSingular, entries);
collectionMap.put(workspaceHandle + "|" + collectionPlural, entries);
}
} else {
// Fallback to two collections. One collection for accepting entries
// and other collection for other ( resources/uploaded images etc.)
final String[] catNames = new String[] { "general", "category1", "category2" };
final FileBasedCollection entries = new FileBasedCollection("Entries", workspaceHandle, "entries", "entry", "application/atom+xml;type=entry",
inlineCategories, catNames, relativeURIs, contextURI, contextPath, servletPath, baseDir);
workspace.addCollection(entries);
// want to be able to look up collection by singular and plural names
collectionMap.put(workspaceHandle + "|entry", entries);
collectionMap.put(workspaceHandle + "|entries", entries);
final FileBasedCollection resources = new FileBasedCollection("Resources", workspaceHandle, "resources", "resource", "*/*", inlineCategories,
catNames, relativeURIs, contextURI, contextPath, servletPath, baseDir);
// want to be able to look up collection by singular and plural names
workspace.addCollection(resources);
collectionMap.put(workspaceHandle + "|resource", resources);
collectionMap.put(workspaceHandle + "|resources", resources);
}
getWorkspaces().add(workspace);
}
/**
* Find workspace by handle, returns null of not found.
*/
FileBasedWorkspace findWorkspaceByHandle(final String handle) {
return workspaceMap.get(handle);
}
FileBasedCollection findCollectionByHandle(final String handle, final String collection) {
return collectionMap.get(handle + "|" + collection);
}
}

View file

@ -0,0 +1,815 @@
/*
* 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.rometools.propono.atom.server.impl;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import org.jdom2.Document;
import org.jdom2.output.XMLOutputter;
import com.rometools.propono.atom.common.Categories;
import com.rometools.propono.atom.common.Collection;
import com.rometools.propono.atom.common.rome.AppModule;
import com.rometools.propono.atom.common.rome.AppModuleImpl;
import com.rometools.propono.atom.server.AtomException;
import com.rometools.propono.atom.server.AtomMediaResource;
import com.rometools.propono.atom.server.AtomNotFoundException;
import com.rometools.propono.utils.Utilities;
import com.rometools.rome.feed.WireFeed;
import com.rometools.rome.feed.atom.Category;
import com.rometools.rome.feed.atom.Content;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Feed;
import com.rometools.rome.feed.atom.Link;
import com.rometools.rome.feed.module.Module;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.WireFeedInput;
import com.rometools.rome.io.WireFeedOutput;
import com.rometools.rome.io.impl.Atom10Generator;
import com.rometools.rome.io.impl.Atom10Parser;
/**
* File based Atom collection implementation. This is the heart of the file-based Atom service
* implementation. It provides methods for adding, getting updating and deleting Atom entries and
* media entries.
*/
public class FileBasedCollection extends Collection {
private String handle = null;
private String singular = null;
private String collection = null;
private boolean inlineCats = false;
private String[] catNames = null;
private boolean relativeURIs = false;
private String contextURI = null;
private String servletPath = null;
private String baseDir = null;
private static final String FEED_TYPE = "atom_1.0";
/**
* Construct by providing title (plain text, no HTML), a workspace handle, a plural collection
* name (e.g. entries), a singular collection name (e.g. entry), the base directory for file
* storage, the content-type range accepted by the collection and the root Atom protocol URI for
* the service.
*
* @param title Title of collection (plain text, no HTML)
* @param handle Workspace handle
* @param collection Collection handle, plural
* @param singular Collection handle, singular
* @param accept Content type range accepted by collection
* @param inlineCats True for inline categories
* @param catNames Category names for this workspace
* @param baseDir Base directory for file storage
* @param relativeURIs True for relative URIs
* @param contextURI Absolute URI of context that hosts APP service
* @param contextPath Context path of APP service (e.g. "/sample-atomserver")
* @param servletPath Servlet path of APP service (e.g. "/app")
*/
public FileBasedCollection(final String title, final String handle, final String collection, final String singular, final String accept,
final boolean inlineCats, final String[] catNames, final boolean relativeURIs, final String contextURI, final String contextPath,
final String servletPath, final String baseDir) {
super(title, "text", relativeURIs ? servletPath.substring(1) + "/" + handle + "/" + collection : contextURI + servletPath + "/" + handle + "/"
+ collection);
this.handle = handle;
this.collection = collection;
this.singular = singular;
this.inlineCats = inlineCats;
this.catNames = catNames;
this.baseDir = baseDir;
this.relativeURIs = relativeURIs;
this.contextURI = contextURI;
this.servletPath = servletPath;
addAccept(accept);
}
/**
* Get feed document representing collection.
*
* @throws com.rometools.rome.propono.atom.server.AtomException On error retrieving feed file.
* @return Atom Feed representing collection.
*/
public Feed getFeedDocument() throws AtomException {
InputStream in = null;
synchronized (FileStore.getFileStore()) {
in = FileStore.getFileStore().getFileInputStream(getFeedPath());
if (in == null) {
in = createDefaultFeedDocument(contextURI + servletPath + "/" + handle + "/" + collection);
}
}
try {
final WireFeedInput input = new WireFeedInput();
final WireFeed wireFeed = input.build(new InputStreamReader(in, "UTF-8"));
return (Feed) wireFeed;
} catch (final Exception ex) {
throw new AtomException(ex);
}
}
/**
* Get list of one Categories object containing categories allowed by collection.
*
* @param inline True if Categories object should contain collection of in-line Categories
* objects or false if it should set the Href for out-of-line categories.
*/
public List<Categories> getCategories(final boolean inline) {
final Categories cats = new Categories();
cats.setFixed(true);
cats.setScheme(contextURI + "/" + handle + "/" + singular);
if (inline) {
for (final String catName : catNames) {
final Category cat = new Category();
cat.setTerm(catName);
cats.addCategory(cat);
}
} else {
cats.setHref(getCategoriesURI());
}
return Collections.singletonList(cats);
}
/**
* Get list of one Categories object containing categories allowed by collection, returns
* in-line categories if collection set to use in-line categories.
*/
@Override
public List<Categories> getCategories() {
return getCategories(inlineCats);
}
/**
* Add entry to collection.
*
* @param entry Entry to be added to collection. Entry will be saved to disk in a directory
* under the collection's directory and the path will follow the pattern
* [collection-plural]/[entryid]/entry.xml. The entry will be added to the
* collection's feed in [collection-plural]/feed.xml.
* @throws java.lang.Exception On error.
* @return Entry as it exists on the server.
*/
public Entry addEntry(final Entry entry) throws Exception {
synchronized (FileStore.getFileStore()) {
final Feed f = getFeedDocument();
final String fsid = FileStore.getFileStore().getNextId();
updateTimestamps(entry);
// Save entry to file
final String entryPath = getEntryPath(fsid);
final OutputStream os = FileStore.getFileStore().getFileOutputStream(entryPath);
updateEntryAppLinks(entry, fsid, true);
Atom10Generator.serializeEntry(entry, new OutputStreamWriter(os, "UTF-8"));
os.flush();
os.close();
// Update feed file
updateEntryAppLinks(entry, fsid, false);
updateFeedDocumentWithNewEntry(f, entry);
return entry;
}
}
/**
* Add media entry to collection. Accepts a media file to be added to collection. The file will
* be saved to disk in a directory under the collection's directory and the path will follow the
* pattern <code>[collection-plural]/[entryid]/media/[entryid]</code>. An Atom entry will be
* created to store metadata for the entry and it will exist at the path
* <code>[collection-plural]/[entryid]/entry.xml</code>. 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(final Entry entry, final String slug, final InputStream is) throws Exception {
synchronized (FileStore.getFileStore()) {
// Save media file temp file
final Content content = entry.getContents().get(0);
if (entry.getTitle() == null) {
entry.setTitle(slug);
}
final String fileName = createFileName(slug != null ? slug : entry.getTitle(), content.getType());
final File tempFile = File.createTempFile(fileName, "tmp");
final FileOutputStream fos = new FileOutputStream(tempFile);
Utilities.copyInputToOutput(is, fos);
fos.close();
// Save media file
final FileInputStream fis = new FileInputStream(tempFile);
saveMediaFile(fileName, content.getType(), tempFile.length(), fis);
fis.close();
final File resourceFile = new File(getEntryMediaPath(fileName));
// Create media-link entry
updateTimestamps(entry);
// Save media-link entry
final String entryPath = getEntryPath(fileName);
final 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
final 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());
}
final String entryPath = getEntryPath(fsid);
checkExistence(entryPath);
final InputStream in = FileStore.getFileStore().getFileInputStream(entryPath);
final Entry entry;
final 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(final String fileName) throws Exception {
final String filePath = getEntryMediaPath(fileName);
final 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(final Entry entry, String fsid) throws Exception {
synchronized (FileStore.getFileStore()) {
final 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);
final String entryPath = getEntryPath(fsid);
final 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(final String fileName, final String contentType, final InputStream is) throws Exception {
synchronized (FileStore.getFileStore()) {
final File tempFile = File.createTempFile(fileName, "tmp");
final FileOutputStream fos = new FileOutputStream(tempFile);
Utilities.copyInputToOutput(is, fos);
fos.close();
// Update media file
final FileInputStream fis = new FileInputStream(tempFile);
saveMediaFile(fileName, contentType, tempFile.length(), fis);
fis.close();
final File resourceFile = new File(getEntryMediaPath(fileName));
// Load media-link entry to return
final String entryPath = getEntryPath(fileName);
final InputStream in = FileStore.getFileStore().getFileInputStream(entryPath);
final Entry atomEntry = loadAtomResourceEntry(in, resourceFile);
updateTimestamps(atomEntry);
updateMediaEntryAppLinks(atomEntry, fileName, false);
// Update feed with new entry
final Feed f = getFeedDocument();
updateFeedDocumentWithExistingEntry(f, atomEntry);
// Save updated media-link entry
final 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(final String fsid) throws Exception {
synchronized (FileStore.getFileStore()) {
// Remove entry from Feed
final Feed feed = getFeedDocument();
updateFeedDocumentRemovingEntry(feed, fsid);
final String entryFilePath = getEntryPath(fsid);
FileStore.getFileStore().deleteFile(entryFilePath);
final String entryMediaPath = getEntryMediaPath(fsid);
if (entryMediaPath != null) {
FileStore.getFileStore().deleteFile(entryMediaPath);
}
final String entryDirPath = getEntryDirPath(fsid);
FileStore.getFileStore().deleteDirectory(entryDirPath);
try {
Thread.sleep(500L);
} catch (final Exception ignored) {
}
}
}
private void updateFeedDocumentWithNewEntry(final Feed f, final Entry e) throws AtomException {
boolean inserted = false;
for (int i = 0; i < f.getEntries().size(); i++) {
final Entry entry = f.getEntries().get(i);
final AppModule mod = (AppModule) entry.getModule(AppModule.URI);
final AppModule newMod = (AppModule) e.getModule(AppModule.URI);
if (newMod.getEdited().before(mod.getEdited())) {
f.getEntries().add(i, e);
inserted = true;
break;
}
}
if (!inserted) {
f.getEntries().add(0, e);
}
updateFeedDocument(f);
}
private void updateFeedDocumentRemovingEntry(final Feed f, final String id) throws AtomException {
final Entry e = findEntry("urn:uuid:" + id, f);
f.getEntries().remove(e);
updateFeedDocument(f);
}
private void updateFeedDocumentWithExistingEntry(final Feed f, final Entry e) throws AtomException {
final Entry old = findEntry(e.getId(), f);
f.getEntries().remove(old);
boolean inserted = false;
for (int i = 0; i < f.getEntries().size(); i++) {
final Entry entry = f.getEntries().get(i);
final AppModule entryAppModule = (AppModule) entry.getModule(AppModule.URI);
final AppModule eAppModule = (AppModule) entry.getModule(AppModule.URI);
if (eAppModule.getEdited().before(entryAppModule.getEdited())) {
f.getEntries().add(i, e);
inserted = true;
break;
}
}
if (!inserted) {
f.getEntries().add(0, e);
}
updateFeedDocument(f);
}
private Entry findEntry(final String id, final Feed feed) {
for (final Entry entry : feed.getEntries()) {
if (id.equals(entry.getId())) {
return entry;
}
}
return null;
}
private void updateFeedDocument(final Feed f) throws AtomException {
try {
synchronized (FileStore.getFileStore()) {
final WireFeedOutput wireFeedOutput = new WireFeedOutput();
final Document feedDoc = wireFeedOutput.outputJDom(f);
final XMLOutputter outputter = new XMLOutputter();
// outputter.setFormat(Format.getPrettyFormat());
final OutputStream fos = FileStore.getFileStore().getFileOutputStream(getFeedPath());
outputter.output(feedDoc, new OutputStreamWriter(fos, "UTF-8"));
}
} catch (final FeedException fex) {
throw new AtomException(fex);
} catch (final IOException ex) {
throw new AtomException(ex);
}
}
private InputStream createDefaultFeedDocument(final String uri) throws AtomException {
final Feed f = new Feed();
f.setTitle("Feed");
f.setId(uri);
f.setFeedType(FEED_TYPE);
final Link selfLink = new Link();
selfLink.setRel("self");
selfLink.setHref(uri);
f.getOtherLinks().add(selfLink);
try {
final WireFeedOutput wireFeedOutput = new WireFeedOutput();
final Document feedDoc = wireFeedOutput.outputJDom(f);
final XMLOutputter outputter = new XMLOutputter();
// outputter.setFormat(Format.getCompactFormat());
final OutputStream fos = FileStore.getFileStore().getFileOutputStream(getFeedPath());
outputter.output(feedDoc, new OutputStreamWriter(fos, "UTF-8"));
} catch (final FeedException ex) {
throw new AtomException(ex);
} catch (final IOException ex) {
throw new AtomException(ex);
} catch (final Exception e) {
e.printStackTrace();
}
return FileStore.getFileStore().getFileInputStream(getFeedPath());
}
private Entry loadAtomResourceEntry(final InputStream in, final File file) {
try {
final Entry entry = Atom10Parser.parseEntry(new BufferedReader(new InputStreamReader(in)), null, Locale.US);
updateMediaEntryAppLinks(entry, file.getName(), true);
return entry;
} catch (final Exception e) {
e.printStackTrace();
return null;
}
}
private void updateEntryAppLinks(final Entry entry, final String fsid, final boolean singleEntry) {
entry.setId("urn:uuid:" + fsid);
// Look for existing alt links and the alt link
Link altLink = null;
List<Link> altLinks = entry.getAlternateLinks();
if (altLinks != null) {
for (final Link link : altLinks) {
if (link.getRel() == null || "alternate".equals(link.getRel())) {
altLink = link;
break;
}
}
} else {
// No alt links found, so add them now
altLinks = new ArrayList<Link>();
entry.setAlternateLinks(altLinks);
}
// The alt link not found, so add it now
if (altLink == null) {
altLink = new Link();
altLinks.add(altLink);
}
// Set correct value for the alt link
altLink.setRel("alternate");
altLink.setHref(getEntryViewURI(fsid));
// Look for existing other links and the edit link
Link editLink = null;
List<Link> otherLinks = entry.getOtherLinks();
if (otherLinks != null) {
for (final Link link : otherLinks) {
if ("edit".equals(link.getRel())) {
editLink = link;
break;
}
}
} else {
// No other links found, so add them now
otherLinks = new ArrayList<Link>();
entry.setOtherLinks(otherLinks);
}
// The edit link not found, so add it now
if (editLink == null) {
editLink = new Link();
otherLinks.add(editLink);
}
// Set correct value for the edit link
editLink.setRel("edit");
editLink.setHref(getEntryEditURI(fsid, relativeURIs, singleEntry));
}
private void updateMediaEntryAppLinks(final Entry entry, final String fileName, final boolean singleEntry) {
// TODO: figure out why PNG is missing from Java MIME types
final FileTypeMap map = FileTypeMap.getDefaultFileTypeMap();
if (map instanceof MimetypesFileTypeMap) {
try {
((MimetypesFileTypeMap) map).addMimeTypes("image/png png PNG");
} catch (final Exception ignored) {
}
}
entry.setId(getEntryMediaViewURI(fileName));
entry.setTitle(fileName);
entry.setUpdated(new Date());
final List<Link> otherlinks = new ArrayList<Link>();
entry.setOtherLinks(otherlinks);
final Link editlink = new Link();
editlink.setRel("edit");
editlink.setHref(getEntryEditURI(fileName, relativeURIs, singleEntry));
otherlinks.add(editlink);
final Link editMedialink = new Link();
editMedialink.setRel("edit-media");
editMedialink.setHref(getEntryMediaEditURI(fileName, relativeURIs, singleEntry));
otherlinks.add(editMedialink);
final Content content = entry.getContents().get(0);
content.setSrc(getEntryMediaViewURI(fileName));
final List<Content> contents = new ArrayList<Content>();
contents.add(content);
entry.setContents(contents);
}
/**
* Create a Rome Atom entry based on a Roller entry. Content is escaped. Link is stored as
* rel=alternate link.
*/
private Entry loadAtomEntry(final InputStream in) {
try {
return Atom10Parser.parseEntry(new BufferedReader(new InputStreamReader(in, "UTF-8")), null, Locale.US);
} catch (final Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Update existing or add new app:edited.
*/
private void updateTimestamps(final Entry entry) {
// We're not differenting between an update and an edit (yet)
entry.setUpdated(new Date());
AppModule appModule = (AppModule) entry.getModule(AppModule.URI);
if (appModule == null) {
appModule = new AppModuleImpl();
final List<Module> modules = entry.getModules() == null ? new ArrayList<Module>() : entry.getModules();
modules.add(appModule);
entry.setModules(modules);
}
appModule.setEdited(entry.getUpdated());
}
/**
* Save file to website's resource directory.
*
* @param handle Weblog handle to save to
* @param name Name of file to save
* @param size Size of file to be saved
* @param is Read file from input stream
*/
private void saveMediaFile(final String name, final String contentType, final long size, final InputStream is) throws AtomException {
final byte[] buffer = new byte[8192];
int bytesRead = 0;
final File dirPath = new File(getEntryMediaPath(name));
if (!dirPath.getParentFile().exists()) {
dirPath.getParentFile().mkdirs();
}
OutputStream bos = null;
try {
bos = new FileOutputStream(dirPath.getAbsolutePath());
while ((bytesRead = is.read(buffer, 0, 8192)) != -1) {
bos.write(buffer, 0, bytesRead);
}
} catch (final Exception e) {
throw new AtomException("ERROR uploading file", e);
} finally {
try {
bos.flush();
bos.close();
} catch (final Exception ignored) {
}
}
}
/**
* Creates a file name for a file based on a weblog handle, title string and a content-type.
*
* @param handle Weblog handle
* @param title Title to be used as basis for file name (or null)
* @param contentType Content type of file (must not be null)
*
* If a title is specified, the method will apply the same create-anchor logic we use
* for weblog entries to create a file name based on the title.
*
* If title is null, the base file name will be the weblog handle plus a YYYYMMDDHHSS
* timestamp.
*
* The extension will be formed by using the part of content type that comes after he
* slash.
*
* For example: weblog.handle = "daveblog" title = "Port Antonio" content-type =
* "image/jpg" Would result in port_antonio.jpg
*
* Another example: weblog.handle = "daveblog" title = null content-type =
* "image/jpg" Might result in daveblog-200608201034.jpg
*/
private String createFileName(final String title, final String contentType) {
if (handle == null) {
throw new IllegalArgumentException("weblog handle cannot be null");
}
if (contentType == null) {
throw new IllegalArgumentException("contentType cannot be null");
}
String fileName = null;
final SimpleDateFormat sdf = new SimpleDateFormat();
sdf.applyPattern("yyyyMMddHHssSSS");
// Determine the extension based on the contentType. This is a hack.
// The info we need to map from contentType to file extension is in
// JRE/lib/content-type.properties, but Java Activation doesn't provide
// a way to do a reverse mapping or to get at the data.
final String[] typeTokens = contentType.split("/");
final String ext = typeTokens[1];
if (title != null && !title.trim().equals("")) {
// We've got a title, so use it to build file name
final String base = Utilities.replaceNonAlphanumeric(title, ' ');
final StringTokenizer toker = new StringTokenizer(base);
String tmp = null;
int count = 0;
while (toker.hasMoreTokens() && count < 5) {
String s = toker.nextToken();
s = s.toLowerCase();
tmp = tmp == null ? s : tmp + "_" + s;
count++;
}
fileName = tmp + "-" + sdf.format(new Date()) + "." + ext;
} else {
// No title or text, so instead we'll use the item's date
// in YYYYMMDD format to form the file name
fileName = handle + "-" + sdf.format(new Date()) + "." + ext;
}
return fileName;
}
// ------------------------------------------------------------ URI methods
private String getEntryEditURI(final String fsid, final boolean relative, final boolean singleEntry) {
String entryURI = null;
if (relative) {
if (singleEntry) {
entryURI = fsid;
} else {
entryURI = singular + "/" + fsid;
}
} else {
entryURI = contextURI + servletPath + "/" + handle + "/" + singular + "/" + fsid;
}
return entryURI;
}
private String getEntryViewURI(final String fsid) {
return contextURI + "/" + handle + "/" + collection + "/" + fsid + "/entry.xml";
}
private String getEntryMediaEditURI(final String fsid, final boolean relative, final boolean singleEntry) {
String entryURI = null;
if (relative) {
if (singleEntry) {
entryURI = "media/" + fsid;
} else {
entryURI = singular + "/media/" + fsid;
}
} else {
entryURI = contextURI + servletPath + "/" + handle + "/" + singular + "/media/" + fsid;
}
return entryURI;
}
private String getEntryMediaViewURI(final String fsid) {
return contextURI + "/" + handle + "/" + collection + "/" + fsid + "/media/" + fsid;
}
private String getCategoriesURI() {
if (!relativeURIs) {
return contextURI + servletPath + "/" + handle + "/" + singular + "/categories";
} else {
return servletPath + "/" + handle + "/" + singular + "/categories";
}
}
// ------------------------------------------------------- File path methods
private String getBaseDir() {
return baseDir;
}
private String getFeedPath() {
return getBaseDir() + handle + File.separator + collection + File.separator + "feed.xml";
}
private String getEntryDirPath(final String id) {
return getBaseDir() + handle + File.separator + collection + File.separator + id;
}
private String getEntryPath(final String id) {
return getEntryDirPath(id) + File.separator + "entry.xml";
}
private String getEntryMediaPath(final String id) {
return getEntryDirPath(id) + File.separator + "media" + File.separator + id;
}
private static void checkExistence(final String path) throws AtomNotFoundException {
if (!FileStore.getFileStore().exists(path)) {
throw new AtomNotFoundException("Entry does not exist");
}
}
}

View file

@ -0,0 +1,30 @@
/*
* 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.rometools.propono.atom.server.impl;
import com.rometools.propono.atom.common.Workspace;
/**
* File based Atom service Workspace.
*/
public class FileBasedWorkspace extends Workspace {
/** Creates a new instance of FileBasedWorkspace */
public FileBasedWorkspace(final String handle, final String baseDir) {
super(handle, "text");
}
}

View file

@ -0,0 +1,117 @@
/*
* 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.rometools.propono.atom.server.impl;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class which helps in handling File persistence related operations.
*/
class FileStore {
private static final Logger LOG = LoggerFactory.getLogger(FileStore.class);
private static final FileStore fileStore = new FileStore();
public String getNextId() {
// return UUID.randomUUID().toString(); // requires JDK 1.5
return RandomStringUtils.randomAlphanumeric(20);
}
public boolean exists(final String path) {
final File f = new File(path);
return f.exists();
}
public InputStream getFileInputStream(final String path) {
LOG.debug("getFileContents path: " + path);
try {
return new BufferedInputStream(new FileInputStream(path));
} catch (final FileNotFoundException e) {
LOG.debug(" File not found: " + path);
return null;
}
}
public OutputStream getFileOutputStream(final String path) {
LOG.debug("getFileOutputStream path: " + path);
try {
final File f = new File(path);
f.getParentFile().mkdirs();
return new BufferedOutputStream(new FileOutputStream(f));
} catch (final FileNotFoundException e) {
LOG.debug(" File not found: " + path);
return null;
}
}
public void createOrUpdateFile(final String path, final InputStream content) throws FileNotFoundException, IOException {
LOG.debug("createOrUpdateFile path: " + path);
final File f = new File(path);
f.mkdirs();
final FileOutputStream out = new FileOutputStream(f);
final byte[] buffer = new byte[2048];
int read;
while ((read = content.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
out.close();
}
public void deleteFile(final String path) {
LOG.debug("deleteFile path: " + path);
final File f = new File(path);
if (!f.delete()) {
LOG.debug(" Failed to delete: " + f.getAbsolutePath());
}
}
public static FileStore getFileStore() {
return fileStore;
}
public boolean deleteDirectory(final String path) {
return deleteDirectory(new File(path));
}
public boolean deleteDirectory(final File path) {
LOG.debug("deleteDirectory path: " + path);
if (path.exists()) {
final File[] files = path.listFiles();
for (final File file : files) {
if (file.isDirectory()) {
deleteDirectory(file);
} else {
file.delete();
}
}
}
return path.delete();
}
}

View file

@ -0,0 +1,212 @@
/*
* 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.rometools.propono.blogclient;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Base implementation of a blog entry.
*/
public abstract class BaseBlogEntry implements BlogEntry {
protected String id = null;
protected Person author = null;
protected Content content = null;
protected String title = null;
protected String permalink = null;
protected String summary = null;
protected Date modificationDate = null;
protected Date publicationDate = null;
protected List<Category> categories = new ArrayList<Category>();
protected boolean draft = false;
private Blog blog = null;
/**
* Contruct abstract blog entry.
*/
public BaseBlogEntry(final Blog blog) {
this.blog = blog;
}
/**
* {@inheritDoc}
*/
@Override
public String getId() {
return id;
}
/**
* {@inheritDoc}
*/
@Override
public String getPermalink() {
return permalink;
}
void setPermalink(final String permalink) {
this.permalink = permalink;
}
/**
* {@inheritDoc}
*/
@Override
public Person getAuthor() {
return author;
}
/**
* {@inheritDoc}
*/
@Override
public void setAuthor(final Person author) {
this.author = author;
}
/**
* {@inheritDoc}
*/
@Override
public Content getContent() {
return content;
}
/**
* {@inheritDoc}
*/
@Override
public void setContent(final Content content) {
this.content = content;
}
/**
* {@inheritDoc}
*/
@Override
public boolean getDraft() {
return draft;
}
/**
* {@inheritDoc}
*/
@Override
public void setDraft(final boolean draft) {
this.draft = draft;
}
/**
* {@inheritDoc}
*/
@Override
public Date getPublicationDate() {
return publicationDate;
}
/**
* {@inheritDoc}
*/
@Override
public void setPublicationDate(final Date pubDate) {
publicationDate = pubDate;
}
/**
* {@inheritDoc}
*/
@Override
public Date getModificationDate() {
return modificationDate;
}
/**
* {@inheritDoc}
*/
@Override
public void setModificationDate(final Date date) {
modificationDate = date;
}
/**
* {@inheritDoc}
*/
@Override
public String getTitle() {
return title;
}
/**
* {@inheritDoc}
*/
@Override
public void setTitle(final String title) {
this.title = title;
}
/**
* {@inheritDoc}
*/
@Override
public String getSummary() {
return summary;
}
/**
* {@inheritDoc}
*/
@Override
public void setSummary(final String summary) {
this.summary = summary;
}
/**
* {@inheritDoc}
*/
@Override
public List<Category> getCategories() {
return categories;
}
/**
* {@inheritDoc}
*/
@Override
public void setCategories(final List<Category> categories) {
this.categories = categories;
}
/**
* {@inheritDoc}
*/
@Override
public Blog getBlog() {
return blog;
}
void setBlog(final Blog blog) {
this.blog = blog;
}
/**
* String representation, returns id.
*/
@Override
public String toString() {
return id;
}
}

View file

@ -0,0 +1,235 @@
/*
* 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.rometools.propono.blogclient;
import java.util.Iterator;
import java.util.List;
/**
* <p>
* Represents 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.
* </p>
*/
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.rometools.rome.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<Collection> 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<String> 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<BlogEntry.Category> 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<BlogEntry> 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.
*/
@Deprecated
public Iterator<BlogEntry> 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.
*/
@Deprecated
public Iterator<BlogEntry> 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.
*/
@Deprecated
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.
*/
@Deprecated
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.
*/
@Deprecated
public List<BlogEntry.Category> getCategories() throws BlogClientException;
}

View file

@ -0,0 +1,44 @@
/*
* 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.rometools.propono.blogclient;
/**
* Represents a Blog Client exception, the library throws these instead of implementation specific
* exceptions.
*/
public class BlogClientException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Construct a new exception
*
* @param msg Text message that explains exception
*/
public BlogClientException(final 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(final String msg, final Throwable t) {
super(msg, t);
}
}

View file

@ -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.rometools.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<Blog> getBlogs();
/** Get blog by token */
public abstract Blog getBlog(String token);
/** Set appkey (optional, needed by some blog servers) */
public void setAppkey(String appkey);
}

View file

@ -0,0 +1,74 @@
/*
* 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.rometools.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.rometools.propono.blogclient.atomprotocol.AtomConnection";
private static String METAWEBLOG_IMPL_CLASS = "com.rometools.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(final String type, final String url, final String username, final 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(final String className, final String url, final String username, final String password)
throws BlogClientException {
Class<?> conClass;
try {
conClass = Class.forName(className);
} catch (final ClassNotFoundException ex) {
throw new BlogClientException("BlogConnection impl. class not found: " + className, ex);
}
final 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 (final Throwable t) {
throw new BlogClientException("ERROR instantiating BlogConnection impl.", t);
}
}
}

View file

@ -0,0 +1,261 @@
/*
* 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.rometools.propono.blogclient;
import java.util.Date;
import java.util.List;
/**
* 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<Category> getCategories();
/** Set categories, a list of BlogEntry.Category objects */
public void setCategories(List<Category> 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(final 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(final 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(final 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(final 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(final String email) {
this.email = email;
}
/** Get person's name */
public String getName() {
return name;
}
/** Set person's name */
public void setName(final String name) {
this.name = name;
}
/** Get person's URL */
public String getUrl() {
return url;
}
/** Set person's URL */
public void setUrl(final String url) {
this.url = url;
}
/** Returns person's name */
@Override
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(final String id) {
this.id = id;
name = id;
}
/**
* Determines if categories are equal based on id.
*/
@Override
public boolean equals(final Object obj) {
final 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(final String id) {
this.id = id;
}
/** Get category display name */
public String getName() {
return name;
}
/** Set category display name */
public void setName(final String name) {
this.name = name;
}
/** Get URL of category domain */
public String getUrl() {
return url;
}
/** Set URL of category domain */
public void setUrl(final String url) {
this.url = url;
}
/** Return category's name or id for display */
@Override
public String toString() {
return name != null ? name : id;
}
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.rometools.propono.blogclient;
import java.io.InputStream;
/**
* Represents a file that has been uploaded to a blog.
* <p />
* 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;
}

View file

@ -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.rometools.propono.blogclient.atomprotocol;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.rometools.propono.atom.client.ClientAtomService;
import com.rometools.propono.atom.client.ClientCollection;
import com.rometools.propono.atom.client.ClientEntry;
import com.rometools.propono.atom.client.ClientMediaEntry;
import com.rometools.propono.atom.client.ClientWorkspace;
import com.rometools.propono.blogclient.Blog;
import com.rometools.propono.blogclient.BlogClientException;
import com.rometools.propono.blogclient.BlogEntry;
import com.rometools.propono.blogclient.BlogResource;
import com.rometools.propono.utils.ProponoException;
/**
* Atom protocol implementation of the BlogClient Blog interface.
*/
public class AtomBlog implements Blog {
private String name = null;
private ClientAtomService service;
private ClientWorkspace workspace = null;
private AtomCollection entriesCollection = null;
private AtomCollection resourcesCollection = null;
private final Map<String, AtomCollection> collections = new TreeMap<String, AtomCollection>();
/**
* 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(final ClientAtomService service, final ClientWorkspace workspace) {
setService(service);
setWorkspace(workspace);
name = workspace.getTitle();
final List<com.rometools.propono.atom.common.Collection> collect = workspace.getCollections();
final Iterator<com.rometools.propono.atom.common.Collection> members = collect.iterator();
while (members.hasNext()) {
final 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}
*/
@Override
public String getName() {
return name;
}
/**
* String display of blog, returns name.
*/
@Override
public String toString() {
return getName();
}
/**
* {@inheritDoc}
*/
@Override
public String getToken() {
return entriesCollection.getToken();
}
/**
* {@inheritDoc}
*/
@Override
public BlogEntry newEntry() throws BlogClientException {
if (entriesCollection == null) {
throw new BlogClientException("No entry collection");
}
return entriesCollection.newEntry();
}
/**
* {@inheritDoc}
*/
@Override
public BlogEntry getEntry(final String token) throws BlogClientException {
ClientEntry clientEntry = null;
try {
clientEntry = getService().getEntry(token);
} catch (final 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}
*/
@Override
public Iterator<BlogEntry> getEntries() throws BlogClientException {
if (entriesCollection == null) {
throw new BlogClientException("No primary entry collection");
}
return new AtomEntryIterator(entriesCollection);
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<BlogEntry> getResources() throws BlogClientException {
if (resourcesCollection == null) {
throw new BlogClientException("No primary entry collection");
}
return new AtomEntryIterator(resourcesCollection);
}
String saveEntry(final BlogEntry entry) throws BlogClientException {
if (entriesCollection == null) {
throw new BlogClientException("No primary entry collection");
}
return entriesCollection.saveEntry(entry);
}
void deleteEntry(final BlogEntry entry) throws BlogClientException {
if (entriesCollection == null) {
throw new BlogClientException("No primary entry collection");
}
entriesCollection.deleteEntry(entry);
}
/**
* {@inheritDoc}
*/
@Override
public List<BlogEntry.Category> getCategories() throws BlogClientException {
if (entriesCollection == null) {
throw new BlogClientException("No primary entry collection");
}
return entriesCollection.getCategories();
}
/**
* {@inheritDoc}
*/
@Override
public BlogResource newResource(final String name, final String contentType, final byte[] bytes) throws BlogClientException {
if (resourcesCollection == null) {
throw new BlogClientException("No resource collection");
}
return resourcesCollection.newResource(name, contentType, bytes);
}
String saveResource(final BlogResource res) throws BlogClientException {
if (resourcesCollection == null) {
throw new BlogClientException("No primary resource collection");
}
return resourcesCollection.saveResource(res);
}
void deleteResource(final BlogResource resource) throws BlogClientException {
deleteEntry(resource);
}
/**
* {@inheritDoc}
*/
@Override
public List<Collection> getCollections() throws BlogClientException {
return new ArrayList<Collection>(collections.values());
}
/**
* {@inheritDoc}
*/
@Override
public Blog.Collection getCollection(final String token) throws BlogClientException {
return collections.get(token);
}
ClientAtomService getService() {
return service;
}
void setService(final ClientAtomService service) {
this.service = service;
}
ClientWorkspace getWorkspace() {
return workspace;
}
void setWorkspace(final ClientWorkspace workspace) {
this.workspace = workspace;
}
}

View file

@ -0,0 +1,173 @@
/*
* 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.rometools.propono.blogclient.atomprotocol;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.rometools.propono.atom.client.ClientAtomService;
import com.rometools.propono.atom.client.ClientCollection;
import com.rometools.propono.atom.client.ClientEntry;
import com.rometools.propono.atom.common.Categories;
import com.rometools.propono.blogclient.Blog;
import com.rometools.propono.blogclient.BlogClientException;
import com.rometools.propono.blogclient.BlogEntry;
import com.rometools.propono.blogclient.BlogResource;
import com.rometools.rome.feed.atom.Category;
/**
* Atom protocol implementation of BlogClient Blog.Collection.
*/
public class AtomCollection implements Blog.Collection {
private Blog blog = null;
private List<BlogEntry.Category> categories = new ArrayList<BlogEntry.Category>();
private ClientCollection clientCollection = null;
AtomCollection(final AtomBlog blog, final ClientCollection col) {
this.blog = blog;
clientCollection = col;
for (final Object element : col.getCategories()) {
final Categories cats = (Categories) element;
for (final Object element2 : cats.getCategories()) {
final Category cat = (Category) element2;
final BlogEntry.Category blogCat = new BlogEntry.Category(cat.getTerm());
blogCat.setName(cat.getLabel());
blogCat.setUrl(cat.getScheme());
getCategories().add(blogCat);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public String getTitle() {
return getClientCollection().getTitle();
}
/**
* {@inheritDoc}
*/
@Override
public String getToken() {
return getClientCollection().getHrefResolved();
}
/**
* {@inheritDoc}
*/
@Override
public List<String> getAccepts() {
return getClientCollection().getAccepts();
}
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(final String ct) {
return getClientCollection().accepts(ct);
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<BlogEntry> getEntries() throws BlogClientException {
return new AtomEntryIterator(this);
}
/**
* {@inheritDoc}
*/
@Override
public BlogEntry newEntry() throws BlogClientException {
final AtomBlog atomBlog = (AtomBlog) getBlog();
final BlogEntry entry = new AtomEntry(atomBlog, this);
return entry;
}
/**
* {@inheritDoc}
*/
@Override
public BlogResource newResource(final String name, final String contentType, final byte[] bytes) throws BlogClientException {
return new AtomResource(this, name, contentType, bytes);
}
/**
* {@inheritDoc}
*/
@Override
public String saveResource(final BlogResource res) throws BlogClientException {
((AtomResource) res).setCollection(this);
res.save();
return res.getContent().getSrc();
}
/**
* {@inheritDoc}
*/
@Override
public String saveEntry(final BlogEntry entry) throws BlogClientException {
((AtomEntry) entry).setCollection(this);
entry.save();
return entry.getPermalink();
}
void deleteEntry(final BlogEntry entry) throws BlogClientException {
try {
final ClientAtomService service = ((AtomBlog) getBlog()).getService();
final ClientEntry clientEntry = service.getEntry(entry.getToken());
clientEntry.remove();
} catch (final Exception e) {
throw new BlogClientException("ERROR deleting entry", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public Blog getBlog() {
return blog;
}
void setBlog(final AtomBlog blog) {
this.blog = blog;
}
/**
* {@inheritDoc}
*/
@Override
public List<BlogEntry.Category> getCategories() {
return categories;
}
void setCategories(final List<BlogEntry.Category> categories) {
this.categories = categories;
}
ClientCollection getClientCollection() {
return clientCollection;
}
}

View file

@ -0,0 +1,85 @@
/*
* 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.rometools.propono.blogclient.atomprotocol;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.rometools.propono.atom.client.AtomClientFactory;
import com.rometools.propono.atom.client.BasicAuthStrategy;
import com.rometools.propono.atom.client.ClientAtomService;
import com.rometools.propono.atom.client.ClientWorkspace;
import com.rometools.propono.atom.common.Workspace;
import com.rometools.propono.blogclient.Blog;
import com.rometools.propono.blogclient.BlogClientException;
import com.rometools.propono.blogclient.BlogConnection;
/**
* 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 final Map<String, Blog> blogs = new HashMap<String, Blog>();
/**
* 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(final String uri, final String username, final String password) throws BlogClientException {
try {
final ClientAtomService service = AtomClientFactory.getAtomService(uri, new BasicAuthStrategy(username, password));
final Iterator<Workspace> iter = service.getWorkspaces().iterator();
while (iter.hasNext()) {
final ClientWorkspace workspace = (ClientWorkspace) iter.next();
final Blog blog = new AtomBlog(service, workspace);
blogs.put(blog.getToken(), blog);
}
} catch (final Throwable t) {
throw new BlogClientException("Error connecting to blog server", t);
}
}
/**
* {@inheritDoc}
*/
@Override
public List<Blog> getBlogs() {
return new ArrayList<Blog>(blogs.values());
}
/**
* {@inheritDoc}
*/
@Override
public Blog getBlog(final String token) {
return blogs.get(token);
}
/**
* {@inheritDoc}
*/
@Override
public void setAppkey(final String appkey) {
}
}

View file

@ -0,0 +1,226 @@
/*
* 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.rometools.propono.blogclient.atomprotocol;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.rometools.propono.atom.client.ClientEntry;
import com.rometools.propono.atom.common.rome.AppModule;
import com.rometools.propono.atom.common.rome.AppModuleImpl;
import com.rometools.propono.blogclient.BaseBlogEntry;
import com.rometools.propono.blogclient.BlogClientException;
import com.rometools.propono.blogclient.BlogEntry;
import com.rometools.propono.utils.ProponoException;
import com.rometools.rome.feed.atom.Entry;
import com.rometools.rome.feed.atom.Link;
import com.rometools.rome.feed.module.Module;
import com.rometools.rome.feed.synd.SyndPerson;
/**
* Atom protocol implementation of BlogEntry.
*/
public class AtomEntry extends BaseBlogEntry implements BlogEntry {
String editURI = null;
AtomCollection collection = null;
AtomEntry(final AtomBlog blog, final AtomCollection collection) throws BlogClientException {
super(blog);
this.collection = collection;
}
AtomEntry(final AtomCollection collection, final ClientEntry entry) throws BlogClientException {
this((AtomBlog) collection.getBlog(), collection);
// clientEntry = entry;
copyFromRomeEntry(entry);
}
AtomEntry(final AtomBlog blog, final ClientEntry entry) throws BlogClientException {
super(blog);
// clientEntry = entry;
copyFromRomeEntry(entry);
}
@Override
public String getToken() {
return editURI;
}
AtomCollection getCollection() {
return collection;
}
void setCollection(final AtomCollection collection) {
this.collection = collection;
}
/**
* True if entry's token's are equal.
*/
@Override
public boolean equals(final Object o) {
if (o instanceof AtomEntry) {
final AtomEntry other = (AtomEntry) o;
if (other.getToken() != null && getToken() != null) {
return other.getToken().equals(getToken());
}
}
return false;
}
@Override
public void save() throws BlogClientException {
final boolean create = getToken() == null;
if (create && getCollection() == null) {
throw new BlogClientException("Cannot save entry, no collection");
} else if (create) {
try {
final ClientEntry clientEntry = collection.getClientCollection().createEntry();
copyToRomeEntry(clientEntry);
collection.getClientCollection().addEntry(clientEntry);
copyFromRomeEntry(clientEntry);
} catch (final ProponoException ex) {
throw new BlogClientException("Error saving entry", ex);
}
} else {
try {
final ClientEntry clientEntry = ((AtomBlog) getBlog()).getService().getEntry(getToken());
copyToRomeEntry(clientEntry);
clientEntry.update();
copyFromRomeEntry(clientEntry);
} catch (final ProponoException ex) {
throw new BlogClientException("Error updating entry", ex);
}
}
}
@Override
public void delete() throws BlogClientException {
if (getToken() == null) {
throw new BlogClientException("Cannot delete unsaved entry");
}
try {
final ClientEntry clientEntry = ((AtomBlog) getBlog()).getService().getEntry(editURI);
clientEntry.remove();
} catch (final ProponoException ex) {
throw new BlogClientException("Error removing entry", ex);
}
}
void copyFromRomeEntry(final ClientEntry entry) {
id = entry.getId();
title = entry.getTitle();
editURI = entry.getEditURI();
final List<Link> altlinks = entry.getAlternateLinks();
if (altlinks != null) {
for (final Link link : altlinks) {
if ("alternate".equals(link.getRel()) || link.getRel() == null) {
permalink = link.getHrefResolved();
break;
}
}
}
final List<com.rometools.rome.feed.atom.Content> contents = entry.getContents();
com.rometools.rome.feed.atom.Content romeContent = null;
if (contents != null && !contents.isEmpty()) {
romeContent = contents.get(0);
}
if (romeContent != null) {
content = new BlogEntry.Content(romeContent.getValue());
content.setType(romeContent.getType());
content.setSrc(romeContent.getSrc());
}
if (entry.getCategories() != null) {
final List<Category> cats = new ArrayList<Category>();
final List<com.rometools.rome.feed.atom.Category> romeCats = entry.getCategories();
for (final com.rometools.rome.feed.atom.Category romeCat : romeCats) {
final BlogEntry.Category cat = new BlogEntry.Category();
cat.setId(romeCat.getTerm());
cat.setUrl(romeCat.getScheme());
cat.setName(romeCat.getLabel());
cats.add(cat);
}
categories = cats;
}
final List<SyndPerson> authors = entry.getAuthors();
if (authors != null && !authors.isEmpty()) {
final com.rometools.rome.feed.atom.Person romeAuthor = (com.rometools.rome.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();
final AppModule control = (AppModule) entry.getModule(AppModule.URI);
if (control != null && control.getDraft() != null) {
draft = control.getDraft().booleanValue();
} else {
draft = false;
}
}
Entry copyToRomeEntry(final ClientEntry entry) {
if (id != null) {
entry.setId(id);
}
entry.setTitle(title);
if (author != null) {
final com.rometools.rome.feed.atom.Person person = new com.rometools.rome.feed.atom.Person();
person.setName(author.getName());
person.setEmail(author.getEmail());
person.setUrl(author.getUrl());
final List<SyndPerson> authors = new ArrayList<SyndPerson>();
authors.add(person);
entry.setAuthors(authors);
}
if (content != null) {
final com.rometools.rome.feed.atom.Content romeContent = new com.rometools.rome.feed.atom.Content();
romeContent.setValue(content.getValue());
romeContent.setType(content.getType());
final List<com.rometools.rome.feed.atom.Content> contents = new ArrayList<com.rometools.rome.feed.atom.Content>();
contents.add(romeContent);
entry.setContents(contents);
}
if (categories != null) {
final List<com.rometools.rome.feed.atom.Category> romeCats = new ArrayList<com.rometools.rome.feed.atom.Category>();
for (final Category cat : categories) {
final com.rometools.rome.feed.atom.Category romeCategory = new com.rometools.rome.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);
final List<Module> modules = new ArrayList<Module>();
final AppModule control = new AppModuleImpl();
control.setDraft(new Boolean(draft));
modules.add(control);
entry.setModules(modules);
return entry;
}
}

View file

@ -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.rometools.propono.blogclient.atomprotocol;
import java.util.Iterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.propono.atom.client.ClientEntry;
import com.rometools.propono.atom.client.ClientMediaEntry;
import com.rometools.propono.blogclient.BlogClientException;
import com.rometools.propono.blogclient.BlogEntry;
/**
* Atom protocol implementation of BlogClient entry iterator.
*/
public class AtomEntryIterator implements Iterator<BlogEntry> {
private static final Logger LOG = LoggerFactory.getLogger(AtomEntryIterator.class);
private Iterator<ClientEntry> iterator = null;
private AtomCollection collection = null;
AtomEntryIterator(final AtomCollection collection) throws BlogClientException {
try {
this.collection = collection;
iterator = collection.getClientCollection().getEntries();
} catch (final Exception e) {
throw new BlogClientException("ERROR fetching collection", e);
}
}
/**
* True if more entries are available.
*/
@Override
public boolean hasNext() {
return iterator.hasNext();
}
/**
* Get next entry.
*/
@Override
public BlogEntry next() {
try {
final ClientEntry entry = iterator.next();
if (entry instanceof ClientMediaEntry) {
return new AtomResource(collection, (ClientMediaEntry) entry);
} else {
return new AtomEntry(collection, entry);
}
} catch (final Exception e) {
LOG.error("An error occured while fetching entry", e);
}
return null;
}
/**
* Remove is not supported.
*/
@Override
public void remove() {
// optional method, not implemented
}
}

View file

@ -0,0 +1,132 @@
/*
* 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.rometools.propono.blogclient.atomprotocol;
import java.io.InputStream;
import java.util.List;
import com.rometools.propono.atom.client.ClientAtomService;
import com.rometools.propono.atom.client.ClientCollection;
import com.rometools.propono.atom.client.ClientEntry;
import com.rometools.propono.atom.client.ClientMediaEntry;
import com.rometools.propono.blogclient.BlogClientException;
import com.rometools.propono.blogclient.BlogEntry;
import com.rometools.propono.blogclient.BlogResource;
import com.rometools.rome.feed.atom.Link;
/**
* Atom protocol implementation of BlogResource.
*/
public class AtomResource extends AtomEntry implements BlogResource {
private AtomCollection collection;
private byte[] bytes;
AtomResource(final AtomCollection collection, final String name, final String contentType, final byte[] bytes) throws BlogClientException {
super((AtomBlog) collection.getBlog(), collection);
this.collection = collection;
this.bytes = bytes;
final BlogEntry.Content rcontent = new BlogEntry.Content();
rcontent.setType(contentType);
setContent(rcontent);
}
AtomResource(final AtomCollection collection, final ClientMediaEntry entry) throws BlogClientException {
super(collection, entry);
}
AtomResource(final AtomBlog blog, final ClientMediaEntry entry) throws BlogClientException {
super(blog, entry);
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return getTitle();
}
byte[] getBytes() {
return bytes;
}
/**
* {@inheritDoc}
*/
@Override
public InputStream getAsStream() throws BlogClientException {
try {
return null; // ((ClientMediaEntry)clientEntry).getAsStream();
} catch (final Exception e) {
throw new BlogClientException("Error creating entry", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void save() throws BlogClientException {
try {
if (getToken() == null) {
final ClientAtomService clientService = ((AtomBlog) getBlog()).getService();
final ClientCollection clientCollection = collection.getClientCollection();
final ClientMediaEntry clientEntry = new ClientMediaEntry(clientService, clientCollection, getTitle(), "", getContent().getType(), getBytes());
copyToRomeEntry(clientEntry);
collection.getClientCollection().addEntry(clientEntry);
editURI = clientEntry.getEditURI();
} else {
final ClientAtomService clientService = ((AtomBlog) getBlog()).getService();
final ClientMediaEntry clientEntry = (ClientMediaEntry) clientService.getEntry(editURI);
clientEntry.update();
}
} catch (final Exception e) {
throw new BlogClientException("Error creating entry", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void update(final byte[] newBytes) throws BlogClientException {
try {
// ((ClientMediaEntry)clientEntry).setBytes(newBytes);
// clientEntry.update();
} catch (final Exception e) {
throw new BlogClientException("Error creating entry", e);
}
}
@Override
void copyFromRomeEntry(final ClientEntry entry) {
super.copyFromRomeEntry(entry);
final List<Link> links = entry.getOtherLinks();
if (links != null) {
for (final Link link : links) {
if ("edit-media".equals(link.getRel())) {
id = link.getHrefResolved();
break;
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -0,0 +1,467 @@
/*
* 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.rometools.propono.blogclient.metaweblog;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import com.rometools.propono.blogclient.Blog;
import com.rometools.propono.blogclient.BlogClientException;
import com.rometools.propono.blogclient.BlogEntry;
import com.rometools.propono.blogclient.BlogEntry.Category;
import com.rometools.propono.blogclient.BlogResource;
/**
* Blog implementation that uses a mix of Blogger and MetaWeblog API methods.
*/
public class MetaWeblogBlog implements Blog {
private final String blogid;
private final String name;
private final URL url;
private final String userName;
private final String password;
private final Map<String, MetaWeblogBlogCollection> collections;
private String appkey = "dummy";
private XmlRpcClient xmlRpcClient = null;
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return name;
}
/**
* {@inheritDoc}
*/
@Override
public String getToken() {
return blogid;
}
/**
* String representation of blog, returns the name.
*/
@Override
public String toString() {
return getName();
}
private XmlRpcClient getXmlRpcClient() {
if (xmlRpcClient == null) {
final XmlRpcClientConfigImpl xmlrpcConfig = new XmlRpcClientConfigImpl();
xmlrpcConfig.setServerURL(url);
xmlRpcClient = new XmlRpcClient();
xmlRpcClient.setConfig(xmlrpcConfig);
}
return xmlRpcClient;
}
MetaWeblogBlog(final String blogid, final String name, final URL url, final String userName, final String password) {
this.blogid = blogid;
this.name = name;
this.url = url;
this.userName = userName;
this.password = password;
collections = new TreeMap<String, MetaWeblogBlogCollection>();
collections.put("entries", new MetaWeblogBlogCollection(this, "entries", "Entries", "entry"));
collections.put("resources", new MetaWeblogBlogCollection(this, "resources", "Resources", "*"));
}
MetaWeblogBlog(final String blogId, final String name, final URL url, final String userName, final String password, final String appkey) {
this(blogId, name, url, userName, password);
this.appkey = appkey;
}
/**
* {@inheritDoc}
*/
@Override
public BlogEntry newEntry() {
return new MetaWeblogEntry(this, new HashMap<String, Object>());
}
String saveEntry(final BlogEntry entry) throws BlogClientException {
final Blog.Collection col = collections.get("entries");
return col.saveEntry(entry);
}
/**
* {@inheritDoc}
*/
@Override
public BlogEntry getEntry(final String id) throws BlogClientException {
try {
final Object[] params = new Object[] { id, userName, password };
final Object response = getXmlRpcClient().execute("metaWeblog.getPost", params);
@SuppressWarnings("unchecked")
final Map<String, Object> result = (Map<String, Object>) response;
return new MetaWeblogEntry(this, result);
} catch (final Exception e) {
throw new BlogClientException("ERROR: XML-RPC error getting entry", e);
}
}
void deleteEntry(final String id) throws BlogClientException {
try {
getXmlRpcClient().execute("blogger.deletePost", new Object[] { appkey, id, userName, password, Boolean.FALSE });
} catch (final Exception e) {
throw new BlogClientException("ERROR: XML-RPC error getting entry", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<BlogEntry> getEntries() throws BlogClientException {
return new EntryIterator();
}
/**
* {@inheritDoc}
*/
@Override
public BlogResource newResource(final String name, final String contentType, final byte[] bytes) throws BlogClientException {
return new MetaWeblogResource(this, name, contentType, bytes);
}
String saveResource(final MetaWeblogResource resource) throws BlogClientException {
final Blog.Collection col = collections.get("resources");
return col.saveResource(resource);
}
BlogResource getResource(final String token) throws BlogClientException {
return null;
}
/**
* {@inheritDoc}
*/
@Override
public NoOpIterator<BlogEntry> getResources() throws BlogClientException {
return new NoOpIterator<BlogEntry>();
}
void deleteResource(final BlogResource resource) throws BlogClientException {
// no-op
}
/**
* {@inheritDoc}
*/
@Override
public List<Category> getCategories() throws BlogClientException {
final ArrayList<Category> ret = new ArrayList<Category>();
try {
final Object result = getXmlRpcClient().execute("metaWeblog.getCategories", new Object[] { blogid, userName, password });
if (result != null && result instanceof HashMap) {
// Standard MetaWeblog API style: struct of struts
@SuppressWarnings("unchecked")
final Map<String, Object> catsmap = (Map<String, Object>) result;
final Set<String> keys = catsmap.keySet();
for (final String key : keys) {
@SuppressWarnings("unchecked")
final Map<String, Object> catmap = (Map<String, Object>) catsmap.get(key);
final BlogEntry.Category category = new BlogEntry.Category(key);
final String description = (String) catmap.get("description");
category.setName(description);
// catmap.get("htmlUrl");
// catmap.get("rssUrl");
ret.add(category);
}
} else if (result != null && result instanceof Object[]) {
// Wordpress style: array of structs
final Object[] array = (Object[]) result;
for (final Object map : array) {
@SuppressWarnings("unchecked")
final Map<String, Object> catmap = (Map<String, Object>) map;
final String categoryId = (String) catmap.get("categoryId");
final String categoryName = (String) catmap.get("categoryName");
final BlogEntry.Category category = new BlogEntry.Category(categoryId);
category.setName(categoryName);
ret.add(category);
}
}
} catch (final Exception e) {
e.printStackTrace();
}
return ret;
}
private Map<String, Object> createPostStructure(final BlogEntry entry) {
return ((MetaWeblogEntry) entry).toPostStructure();
}
/**
* {@inheritDoc}
*/
@Override
public List<Collection> getCollections() throws BlogClientException {
return new ArrayList<Collection>(collections.values());
}
/**
* {@inheritDoc}
*/
@Override
public Blog.Collection getCollection(final String token) throws BlogClientException {
return collections.get(token);
}
// -------------------------------------------------------------------------
/** MetaWeblog API impplementation of Blog.Collection */
public class MetaWeblogBlogCollection implements Blog.Collection {
private String accept = null;
private String title = null;
private String token = null;
private Blog blog = null;
/**
* @param token Identifier for collection, unique within blog
* @param title Title of collection
* @param accept Content types accepted, either "entry" or "*"
*/
public MetaWeblogBlogCollection(final Blog blog, final String token, final String title, final String accept) {
this.blog = blog;
this.accept = accept;
this.title = title;
this.token = token;
}
/**
* {@inheritDoc}
*/
@Override
public String getTitle() {
return title;
}
/**
* {@inheritDoc}
*/
@Override
public String getToken() {
return token;
}
/**
* {@inheritDoc}
*/
@Override
public List<String> getAccepts() {
return Collections.singletonList(accept);
}
/**
* {@inheritDoc}
*/
@Override
public BlogResource newResource(final String name, final String contentType, final byte[] bytes) throws BlogClientException {
return blog.newResource(name, contentType, bytes);
}
/**
* {@inheritDoc}
*/
@Override
public BlogEntry newEntry() throws BlogClientException {
return blog.newEntry();
}
/**
* {@inheritDoc}
*/
@Override
public boolean accepts(final String ct) {
if (accept.equals("*")) {
// everything accepted
return true;
} else if (accept.equals("entry") && ct.equals("application/metaweblog+xml")) {
// entries only accepted and "application/metaweblog+xml" means entry
return true;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public Iterator<BlogEntry> getEntries() throws BlogClientException {
Iterator<BlogEntry> ret = null;
if (accept.equals("entry")) {
ret = MetaWeblogBlog.this.getEntries();
} else {
ret = getResources();
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public String saveEntry(final BlogEntry entry) throws BlogClientException {
String ret = entry.getId();
if (entry.getId() == null) {
try {
ret = (String) getXmlRpcClient().execute("metaWeblog.newPost",
new Object[] { blogid, userName, password, createPostStructure(entry), new Boolean(!entry.getDraft()) });
} catch (final Exception e) {
throw new BlogClientException("ERROR: XML-RPC error saving new entry", e);
}
} else {
try {
getXmlRpcClient().execute("metaWeblog.editPost",
new Object[] { entry.getId(), userName, password, createPostStructure(entry), new Boolean(!entry.getDraft()) });
} catch (final Exception e) {
throw new BlogClientException("ERROR: XML-RPC error updating entry", e);
}
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public String saveResource(final BlogResource res) throws BlogClientException {
final MetaWeblogResource resource = (MetaWeblogResource) res;
try {
final HashMap<String, Object> resmap = new HashMap<String, Object>();
resmap.put("name", resource.getName());
resmap.put("type", resource.getContent().getType());
resmap.put("bits", resource.getBytes());
final Object[] params = new Object[] { blogid, userName, password, resmap };
final Object response = getXmlRpcClient().execute("metaWeblog.newMediaObject", params);
@SuppressWarnings("unchecked")
final Map<String, Object> result = (Map<String, Object>) response;
final String url = (String) result.get("url");
res.getContent().setSrc(url);
return url;
} catch (final Exception e) {
throw new BlogClientException("ERROR: loading or uploading file", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public List<Category> getCategories() throws BlogClientException {
return MetaWeblogBlog.this.getCategories();
}
/**
* {@inheritDoc}
*/
@Override
public Blog getBlog() {
return blog;
}
}
// -------------------------------------------------------------------------
/**
* Iterates over MetaWeblog API entries.
*/
public class EntryIterator implements Iterator<BlogEntry> {
private int pos = 0;
private boolean eod = false;
private static final int BUFSIZE = 30;
private List<Map<String, Object>> results = null;
/**
* Iterator for looping over MetaWeblog API entries.
*/
public EntryIterator() throws BlogClientException {
getNextEntries();
}
/**
* Returns true if more entries are avialable.
*/
@Override
public boolean hasNext() {
if (pos == results.size() && !eod) {
try {
getNextEntries();
} catch (final Exception ignored) {
}
}
return pos < results.size();
}
/**
* Get next entry.
*/
@Override
public BlogEntry next() {
return new MetaWeblogEntry(MetaWeblogBlog.this, results.get(pos++));
}
/**
* Remove is not implemented.
*/
@Override
public void remove() {
}
private void getNextEntries() throws BlogClientException {
final int requestSize = pos + BUFSIZE;
try {
final Object[] params = new Object[] { blogid, userName, password, new Integer(requestSize) };
final Object response = getXmlRpcClient().execute("metaWeblog.getRecentPosts", params);
@SuppressWarnings("unchecked")
final Map<String, Object>[] resultsArray = (Map<String, Object>[]) response;
results = Arrays.asList(resultsArray);
} catch (final Exception e) {
throw new BlogClientException("ERROR: XML-RPC error getting entry", e);
}
if (results.size() < requestSize) {
eod = true;
}
}
}
}

View file

@ -0,0 +1,106 @@
/*
* 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.rometools.propono.blogclient.metaweblog;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.xmlrpc.XmlRpcException;
import org.apache.xmlrpc.client.XmlRpcClient;
import org.apache.xmlrpc.client.XmlRpcClientConfigImpl;
import com.rometools.propono.blogclient.Blog;
import com.rometools.propono.blogclient.BlogClientException;
import com.rometools.propono.blogclient.BlogConnection;
/**
* BlogClient implementation that uses a mix of Blogger and MetaWeblog API methods.
*/
public class MetaWeblogConnection implements BlogConnection {
private URL url = null;
private String userName = null;
private String password = null;
private String appkey = "null";
private Map<String, MetaWeblogBlog> blogs = null;
private XmlRpcClient xmlRpcClient = null;
public MetaWeblogConnection(final String url, final String userName, final String password) throws BlogClientException {
this.userName = userName;
this.password = password;
try {
this.url = new URL(url);
blogs = createBlogMap();
} catch (final Throwable t) {
throw new BlogClientException("ERROR connecting to server", t);
}
}
private XmlRpcClient getXmlRpcClient() {
if (xmlRpcClient == null) {
final XmlRpcClientConfigImpl xmlrpcConfig = new XmlRpcClientConfigImpl();
xmlrpcConfig.setServerURL(url);
xmlRpcClient = new XmlRpcClient();
xmlRpcClient.setConfig(xmlrpcConfig);
}
return xmlRpcClient;
}
/**
* {@inheritDoc}
*/
@Override
public List<Blog> getBlogs() {
return new ArrayList<Blog>(blogs.values());
}
/**
* {@inheritDoc}
*/
private Map<String, MetaWeblogBlog> createBlogMap() throws XmlRpcException, IOException {
final Map<String, MetaWeblogBlog> blogMap = new HashMap<String, MetaWeblogBlog>();
final Object[] results = (Object[]) getXmlRpcClient().execute("blogger.getUsersBlogs", new Object[] { appkey, userName, password });
for (final Object result : results) {
@SuppressWarnings("unchecked")
final Map<String, Object> blog = (Map<String, Object>) result;
final String blogid = (String) blog.get("blogid");
final String name = (String) blog.get("blogName");
blogMap.put(blogid, new MetaWeblogBlog(blogid, name, url, userName, password));
}
return blogMap;
}
/**
* {@inheritDoc}
*/
@Override
public Blog getBlog(final String token) {
return blogs.get(token);
}
/**
* {@inheritDoc}
*/
@Override
public void setAppkey(final String appkey) {
this.appkey = appkey;
}
}

View file

@ -0,0 +1,126 @@
/*
* 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.rometools.propono.blogclient.metaweblog;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.rometools.propono.blogclient.BaseBlogEntry;
import com.rometools.propono.blogclient.BlogClientException;
import com.rometools.propono.blogclient.BlogEntry;
/**
* MetaWeblog API implementation of an entry.
*/
public class MetaWeblogEntry extends BaseBlogEntry {
MetaWeblogEntry(final MetaWeblogBlog blog, final Map<String, Object> entryMap) {
super(blog);
id = (String) entryMap.get("postid");
content = new Content((String) entryMap.get("description"));
// let's pretend MetaWeblog API has a content-type
content.setType("application/metaweblog+xml");
// no way to tell if entry is draft or not
draft = false;
title = (String) entryMap.get("title");
publicationDate = (Date) entryMap.get("dateCreated");
permalink = (String) entryMap.get("permaLink");
// AlexisMP: fix to get the author value populated.
author.setName((String) entryMap.get("userid"));
author.setEmail((String) entryMap.get("author"));
categories = new ArrayList<Category>();
final Object[] catArray = (Object[]) entryMap.get("categories");
if (catArray != null) {
for (final Object element : catArray) {
final Category cat = new Category((String) element);
categories.add(cat);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public String getToken() {
return id;
}
/**
* True if tokens are equal
*/
@Override
public boolean equals(final Object o) {
if (o instanceof MetaWeblogEntry) {
final MetaWeblogEntry other = (MetaWeblogEntry) o;
if (other.id != null && id != null) {
return other.id.equals(id);
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void save() throws BlogClientException {
id = ((MetaWeblogBlog) getBlog()).saveEntry(this);
}
/**
* {@inheritDoc}
*/
@Override
public void delete() throws BlogClientException {
((MetaWeblogBlog) getBlog()).deleteEntry(id);
}
Map<String, Object> toPostStructure() {
final Map<String, Object> struct = new HashMap<String, Object>();
if (getTitle() != null) {
struct.put("title", getTitle());
}
if (getContent() != null && getContent().getValue() != null) {
struct.put("description", getContent().getValue());
}
if (getCategories() != null && !getCategories().isEmpty()) {
final List<String> catArray = new ArrayList<String>();
final List<Category> cats = getCategories();
for (int i = 0; i < cats.size(); i++) {
final BlogEntry.Category cat = cats.get(i);
catArray.add(cat.getName());
}
struct.put("categories", catArray);
}
if (getPublicationDate() != null) {
struct.put("dateCreated", getPublicationDate());
}
if (getId() != null) {
struct.put("postid", getId());
}
return struct;
}
}

View file

@ -0,0 +1,121 @@
/*
* 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.rometools.propono.blogclient.metaweblog;
import java.io.InputStream;
import java.util.HashMap;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import com.rometools.propono.blogclient.BlogClientException;
import com.rometools.propono.blogclient.BlogResource;
/**
* MetaWeblog API implementation of an resource entry.
*/
public class MetaWeblogResource extends MetaWeblogEntry implements BlogResource {
private final MetaWeblogBlog blog;
private final String name;
private final String contentType;
private byte[] bytes;
MetaWeblogResource(final MetaWeblogBlog blog, final String name, final String contentType, final byte[] bytes) {
super(blog, new HashMap<String, Object>());
this.blog = blog;
this.name = name;
this.contentType = contentType;
this.bytes = bytes;
content = new Content();
content.setType(contentType);
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return name;
}
/**
* {@inheritDoc}
*/
@Override
public String getToken() {
return null;
}
/**
* Get content-type of associated media resource.
*/
public String getContentType() {
return contentType;
}
/**
* Get media resource as input stream.
*/
@Override
public InputStream getAsStream() throws BlogClientException {
final HttpClient httpClient = new HttpClient();
final GetMethod method = new GetMethod(permalink);
try {
httpClient.executeMethod(method);
} catch (final Exception e) {
throw new BlogClientException("ERROR: error reading file", e);
}
if (method.getStatusCode() != 200) {
throw new BlogClientException("ERROR HTTP status=" + method.getStatusCode());
}
try {
return method.getResponseBodyAsStream();
} catch (final Exception e) {
throw new BlogClientException("ERROR: error reading file", e);
}
}
/**
* {@inheritDoc}
*/
@Override
public void save() throws BlogClientException {
blog.saveResource(this);
}
/**
* {@inheritDoc}
*/
@Override
public void update(final byte[] bytes) throws BlogClientException {
this.bytes = bytes;
save();
}
/**
* Get resource data as byte array.
*/
public byte[] getBytes() {
return bytes;
}
/**
* Not supported by MetaWeblog API
*/
@Override
public void delete() throws BlogClientException {
}
}

View file

@ -0,0 +1,24 @@
package com.rometools.propono.blogclient.metaweblog;
import java.util.Iterator;
class NoOpIterator<T> implements Iterator<T> {
/** No-op */
@Override
public boolean hasNext() {
return false;
}
/** No-op */
@Override
public T next() {
return null;
}
/** No-op */
@Override
public void remove() {
}
}

View file

@ -0,0 +1,154 @@
/*
* 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.rometools.propono.utils;
import java.io.PrintStream;
import java.io.PrintWriter;
/**
* Base Propono exception class.
*/
public class ProponoException extends Exception {
private static final long serialVersionUID = 1L;
private Throwable mRootCause = null;
/**
* Construct emtpy exception object.
*/
public ProponoException() {
super();
}
/**
* Construct ProponoException with message string.
*
* @param s Error message string.
*/
public ProponoException(final String s) {
super(s);
}
/**
* Construct ProponoException with message string.
*
* @param s Error message string.
*/
public ProponoException(final String s, final String longMessage) {
super(s);
}
/**
* Construct ProponoException, wrapping existing throwable.
*
* @param s Error message
* @param t Existing connection to wrap.
*/
public ProponoException(final String s, final Throwable t) {
super(s);
mRootCause = t;
}
/**
* Construct ProponoException, wrapping existing throwable.
*
* @param s Error message
* @param t Existing connection to wrap.
*/
public ProponoException(final String s, final String longMessage, final Throwable t) {
super(s);
mRootCause = t;
}
/**
* Construct ProponoException, wrapping existing throwable.
*
* @param t Existing exception to be wrapped.
*/
public ProponoException(final Throwable t) {
mRootCause = t;
}
/**
* Get root cause object, or null if none.
*
* @return Root cause or null if none.
*/
public Throwable getRootCause() {
return mRootCause;
}
/**
* Get root cause message.
*
* @return Root cause message.
*/
public String getRootCauseMessage() {
String rcmessage = null;
if (getRootCause() != null) {
if (getRootCause().getCause() != null) {
rcmessage = getRootCause().getCause().getMessage();
}
rcmessage = rcmessage == null ? getRootCause().getMessage() : rcmessage;
rcmessage = rcmessage == null ? super.getMessage() : rcmessage;
rcmessage = rcmessage == null ? "NONE" : rcmessage;
}
return rcmessage;
}
/**
* Print stack trace for exception and for root cause exception if htere is one.
*
* @see java.lang.Throwable#printStackTrace()
*/
@Override
public void printStackTrace() {
super.printStackTrace();
if (mRootCause != null) {
System.out.println("--- ROOT CAUSE ---");
mRootCause.printStackTrace();
}
}
/**
* Print stack trace for exception and for root cause exception if htere is one.
*
* @param s Stream to print to.
*/
@Override
public void printStackTrace(final PrintStream s) {
super.printStackTrace(s);
if (mRootCause != null) {
s.println("--- ROOT CAUSE ---");
mRootCause.printStackTrace(s);
}
}
/**
* Print stack trace for exception and for root cause exception if htere is one.
*
* @param s Writer to write to.
*/
@Override
public void printStackTrace(final PrintWriter s) {
super.printStackTrace(s);
if (null != mRootCause) {
s.println("--- ROOT CAUSE ---");
mRootCause.printStackTrace(s);
}
}
}

View file

@ -0,0 +1,270 @@
/*
* 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.rometools.propono.utils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
/**
* Utilities for file I/O and string manipulation.
*/
public final class Utilities {
private static final String LS = System.getProperty("line.separator");
private Utilities() {
}
/**
* Returns the contents of the file in a byte array (from JavaAlmanac).
*/
public static byte[] getBytesFromFile(final File file) throws IOException {
final InputStream is = new FileInputStream(file);
try {
// Get the size of the file
final long length = file.length();
// You cannot create an array using a long type.
// It needs to be an int type.
// Before converting to an int type, check
// to ensure that file is not larger than Integer.MAX_VALUE.
if (length > Integer.MAX_VALUE) {
// File is too large
}
// Create the byte array to hold the data
final 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());
}
return bytes;
} finally {
is.close();
}
}
/**
* Read input from stream and into string.
*/
public static String streamToString(final InputStream is) throws IOException {
final StringBuffer sb = new StringBuffer();
final 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(final InputStream input, final OutputStream output) throws IOException {
final BufferedInputStream in = new BufferedInputStream(input);
final BufferedOutputStream out = new BufferedOutputStream(output);
final 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 (final IOException ex) {
throw new IOException("Closing file streams, " + ex.getMessage());
}
}
/**
* Replaces occurences of non-alphanumeric characters with a supplied char.
*/
public static String replaceNonAlphanumeric(final String str, final char subst) {
final StringBuffer ret = new StringBuffer(str.length());
final char[] testChars = str.toCharArray();
for (final char testChar : testChars) {
if (Character.isLetterOrDigit(testChar)) {
ret.append(testChar);
} else {
ret.append(subst);
}
}
return ret.toString();
}
/**
* Convert string to string array.
*/
public static String[] stringToStringArray(final String instr, final String delim) throws NoSuchElementException, NumberFormatException {
final StringTokenizer toker = new StringTokenizer(instr, delim);
final 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(final String[] stringArray, final String delim) {
String ret = "";
for (final String element : stringArray) {
if (ret.length() > 0) {
ret = ret + delim + element;
} else {
ret = element;
}
}
return ret;
}
static Pattern absoluteURIPattern = Pattern.compile("^[a-z0-9]*:.*$");
// private static boolean isAbsoluteURI(final String uri) {
// return absoluteURIPattern.matcher(uri).find();
// }
// private static boolean isRelativeURI(final 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(final String baseURI, final 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
// final int slashslash = xmlbase.indexOf("//");
// final 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("..")) {
// final String ret = null;
// final String[] parts = append.split("/");
// for (final String part : parts) {
// if ("..".equals(part)) {
// final int last = base.lastIndexOf("/");
// if (last != -1) {
// base = base.substring(0, last);
// append = append.substring(3, append.length());
// } else {
// break;
// }
// }
// }
// }
// return base + "/" + append;
// }
// /**
// * Strip starting slash from beginning of string.
// */
// private static String stripStartingSlash(String s) {
// if (s != null && s.startsWith("/")) {
// s = s.substring(1, s.length());
// }
// return s;
// }
// /**
// * Strip trailing slash from end of string.
// */
// private static String stripTrailingSlash(String s) {
// if (s != null && s.endsWith("/")) {
// s = s.substring(0, s.length() - 1);
// }
// return s;
// }
}

View file

@ -0,0 +1 @@
rome.propono.version=2.0.0

View file

@ -0,0 +1,7 @@
# Configures Propono APP extension module
atom_1.0.item.ModuleParser.classes=\
com.rometools.propono.atom.common.rome.AppModuleParser
atom_1.0.item.ModuleGenerator.classes=\
com.rometools.propono.atom.common.rome.AppModuleGenerator

View file

@ -0,0 +1,29 @@
-----
ROME Propono Version 0.4
-----
mkurz
-----
2011-08-16 04:52:04.427
-----
ROME Propono Version 0.4
This is the first release of the {{{./index.html}Rome Propono}} publishing library. It's a beta release and will be followed closely by Propono 0.5.
*Downloads
* {{{./rome\-propono\-0.4\-src.zip}rome\-propono\-0.4\-src.zip}}
* {{{./rome\-propono\-0.4.tar.gz}rome\-propono\-0.4.tar.gz}}
* {{{./rome\-propono\-0.4.zip}rome\-propono\-0.4.zip}}
* {{{./rome\-propono\-0.4\-src.tar.gz}rome\-propono\-0.4\-src.tar.gz}}
[]
*API Docs
* {{{https://rome.dev.java.net/apidocs/subprojects/propono/0.4/overview\-summary.html}Propono 0.4 API Docs}}
[]

View file

@ -0,0 +1,39 @@
-----
ROME Propono Version 0.5
-----
mkurz
-----
2011-08-16 04:50:16.111
-----
ROME Propono Version 0.5
This is the second release of the {{{./index.html}Rome Propono}} publishing library. It's a bug fix release follow\-on to 0.4. Here are the changes:
* 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
[]
*Downloads
* {{{./rome\-propono\-0.5\-src.zip}rome\-propono\-0.5\-src.zip}}
* {{{./rome\-propono\-0.5.tar.gz}rome\-propono\-0.5.tar.gz}}
* {{{./rome\-propono\-0.5.zip}rome\-propono\-0.5.zip}}
* {{{./rome\-propono\-0.5\-src.tar.gz}rome\-propono\-0.5\-src.tar.gz}}
[]
*API Docs
* {{{https://rome.dev.java.net/apidocs/subprojects/propono/0.5/overview\-summary.html}Propono 0.5 API Docs}}
[]

View file

@ -0,0 +1,85 @@
-----
ROME Propono Version 0.6
-----
mkurz
-----
2011-08-16 04:48:12.766
-----
ROME Propono Version 0.6
September 30, 2007
This is the third release of the {{{./index.html}Rome Propono}} publishing library. It includes major changes to add support for the final
Atom Publishing Protocol specification relative URIs and out\-of\-line categories. It's an an interim release it includes a pre\-release
version of ROME 0.9.1\-dev. A new version will follow as soon as ROME 0.9.1 (or 1.0) has been finalized.
*Downloads
* {{{./rome\-propono\-0.6\-src.zip}rome\-propono\-0.6\-src.zip}}
* {{{./rome\-propono\-0.6.tar.gz}rome\-propono\-0.6.tar.gz}}
* {{{./rome\-propono\-0.6.zip}rome\-propono\-0.6.zip}}
* {{{./rome\-propono\-0.6\-src.tar.gz}rome\-propono\-0.6\-src.tar.gz}}
[]
*API Docs
* {{{https://rome.dev.java.net/apidocs/subprojects/propono/0.6/overview\-summary.html}Propono 0.6 API Docs}}
[]
*Changes
* 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).
* 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 {{{http://java.net/jira/browse/ROME\-67}http://java.net/jira/browse/ROME\-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 {{{http://java.net/jira/browse/ROME\-68}http://java.net/jira/browse/ROME\-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
[]

View file

@ -0,0 +1,41 @@
-----
ROME Propono Version 1.0
-----
mkurz
-----
2011-08-16 04:44:34.302
-----
ROME Propono Version 1.0
April 2009
ROME Propono 1.0 is coming soon. If you'd like to help out, you can try 1.0 RC1 and provide feedback to us on the ROME dev mail list.
*Downloads
* {{{./rome\-propono\-1.0RC1\-src.zip}rome\-propono\-1.0RC1\-src.zip}}
* {{{./rome\-propono\-1.0RC1.tar.gz}rome\-propono\-1.0RC1.tar.gz}}
* {{{./rome\-propono\-1.0RC1.zip}rome\-propono\-1.0RC1.zip}}
* {{{./rome\-propono\-1.0RC1\-src.tar.gz}rome\-propono\-1.0RC1\-src.tar.gz}}
[]
*API Docs
* {{{https://rome.dev.java.net/apidocs/subprojects/propono/1.0/overview\-summary.html}Propono 1.0 API Docs}}
[]
*Changes
* Updated to ROME 1.0
* Added support for pluggable authentication in the AtomPub client
* Added support for OAuth in the AtomPub client, see Javadocs for details
[]

31
src/site/apt/index.apt Normal file
View file

@ -0,0 +1,31 @@
-----
Home
-----
mkurz
-----
2011-08-16 04:43:25.062
-----
ROME Propono
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, an Atom server framework and a Blog client that supports both Atom protocol and the MetaWeblog API.
Documentation
* See the {{{./apidocs/overview\-summary.html}Propono API docs}} for an explanation of Propono usage, diagrams and code examples.
[]
Releases
* {{{./ROMEProponoVersion1.0.html}ROME Propono Version 1.0 (propono)}} \- Release 1.0 coming soon...
* {{{./ROMEProponoVersion0.6.html}ROME Propono Version 0.6 (propono)}}
* {{{./ROMEProponoVersion0.5.html}ROME Propono Version 0.5 (propono)}}
* {{{./ROMEProponoVersion0.4.html}ROME Propono Version 0.4 (propono)}}
[]

View file

View file

@ -0,0 +1,8 @@
h1 {
padding: 4px 4px 4px 6px;
border: 1px solid #999;
color: #900;
background-color: #ddd;
font-weight:900;
font-size: x-large;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

30
src/site/site.xml Normal file
View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/DECORATION/1.3.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.3.0 http://maven.apache.org/xsd/decoration-1.3.0.xsd"
name="ROME Propono">
<skin>
<groupId>org.apache.maven.skins</groupId>
<artifactId>maven-fluido-skin</artifactId>
<version>1.3.0</version>
</skin>
<bannerLeft>
<name>ROME</name>
<src>images/romelogo.png</src>
<href>http://github.com/rometools/</href>
</bannerLeft>
<publishDate position="right" />
<version position="right" />
<body>
<menu name="ROME Propono">
<item name="Overview" href="index.html" />
</menu>
<menu ref="reports" />
</body>
</project>

View file

@ -0,0 +1,420 @@
/*
* 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.rometools.propono.atom.client;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.junit.Ignore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.propono.atom.common.Categories;
import com.rometools.propono.atom.common.Collection;
import com.rometools.propono.utils.ProponoException;
import com.rometools.rome.feed.atom.Category;
import com.rometools.rome.feed.atom.Content;
/**
* Simple APP test designed to run against a live Atom server.
*/
@Ignore
public class AtomClientTest extends TestCase {
private static Logger log = LoggerFactory.getLogger(AtomClientTest.class);
private static ClientAtomService service = null;
// Basic Auth example
private static String endpoint = "http://localhost:8080/sample-atomserver/app";
private static String username = "admin";
private static String password = "admin";
static {
try {
service = AtomClientFactory.getAtomService(endpoint, new BasicAuthStrategy(username, password));
} catch (final Exception e) {
log.error("ERROR creating service", e);
}
}
/*
* // Roller OAuth example private static String endpoint =
* "http://macsnoopdave:8080/roller-services/app"; private static String consumerKey =
* "55132608a2fb68816bcd3d1caeafc933"; private static String consumerSecret =
* "bb420783-fdea-4270-ab83-36445c18c307"; private static String requestUri =
* "http://macsnoopdave:8080/roller-services/oauth/requestToken"; private static String
* authorizeUri =
* "http://macsnoopdave:8080/roller-services/oauth/authorize?userId=roller&oauth_callback=none";
* private static String accessUri =
* "http://macsnoopdave:8080/roller-services/oauth/accessToken"; private static String username
* = "roller"; private static String password = "n/a"; static { try { service =
* AtomClientFactory.getAtomService(endpoint, new OAuthStrategy( username, consumerKey,
* consumerSecret, "HMAC-SHA1", requestUri, authorizeUri, accessUri)); } catch (Exception e) {
* log.error("ERROR creating service", e); } }
*/
// GData Blogger API
/*
* private static String endpoint =
* "http://www.blogger.com/feeds/default/blogs?alt=atom-service"; private static String email =
* "EMAIL"; private static String password = "PASSWORD"; private static String serviceName =
* "blogger"; static { try { service = AtomClientFactory.getAtomService(endpoint, new
* GDataAuthStrategy(email, password, serviceName)); } catch (Exception e) {
* log.error("ERROR creating service", e); } }
*/
public AtomClientTest(final String testName) {
super(testName);
}
public String getEndpoint() {
return endpoint;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
@Override
protected void setUp() throws Exception {
}
@Override
protected void tearDown() throws Exception {
}
public static Test suite() {
final TestSuite suite = new TestSuite(AtomClientTest.class);
return suite;
}
/**
* Tests that server has introspection doc with at least one workspace.
*/
public void testGetAtomService() throws Exception {
assertNotNull(service);
assertTrue(!service.getWorkspaces().isEmpty());
for (final Object element : service.getWorkspaces()) {
final ClientWorkspace space = (ClientWorkspace) element;
assertNotNull(space.getTitle());
log.debug("Workspace: " + space.getTitle());
for (final Object element2 : space.getCollections()) {
final ClientCollection col = (ClientCollection) element2;
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().isEmpty());
int count = 0;
for (final Object element : service.getWorkspaces()) {
final ClientWorkspace space = (ClientWorkspace) element;
assertNotNull(space.getTitle());
for (final Object element2 : space.getCollections()) {
final ClientCollection col = (ClientCollection) element2;
if (col.accepts(Collection.ENTRY_TYPE)) {
// we found a collection that accepts entries, so post one
final ClientEntry m1 = col.createEntry();
m1.setTitle("Test post");
final 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
final 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 (final 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().isEmpty());
int count = 0;
for (final Object element : service.getWorkspaces()) {
final ClientWorkspace space = (ClientWorkspace) element;
assertNotNull(space.getTitle());
for (final Object element2 : space.getCollections()) {
final ClientCollection col = (ClientCollection) element2;
if (col.accepts(Collection.ENTRY_TYPE)) {
// we found a collection that accepts entries, so post one
final ClientEntry m1 = col.createEntry();
m1.setTitle(col.getTitle() + ": Test post");
final 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
final ClientEntry m2 = col.getEntry(m1.getEditURI());
assertNotNull(m2);
m2.setTitle(col.getTitle() + ": Updated title");
m2.update();
// entry should now be updated on server
final ClientEntry m3 = 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 (final ProponoException e) {
failed = true;
}
assertTrue(failed);
count++;
}
}
}
assertTrue(count > 0);
}
public void testFindWorkspace() throws Exception {
assertNotNull(service);
final ClientWorkspace ws = (ClientWorkspace) service.findWorkspace("adminblog");
if (ws != null) {
final ClientCollection col = (ClientCollection) ws.findCollection(null, "entry");
final ClientEntry entry = col.createEntry();
entry.setTitle("NPE on submitting order query");
entry.setContent("This is a <b>bad</b> one!", Content.HTML);
col.addEntry(entry);
// entry should now exist on server
final ClientEntry saved = 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 (final 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().isEmpty());
int count = 0;
for (final Object element2 : service.getWorkspaces()) {
final ClientWorkspace space = (ClientWorkspace) element2;
assertNotNull(space.getTitle());
for (final Object element3 : space.getCollections()) {
final ClientCollection col = (ClientCollection) element3;
if (col.accepts(Collection.ENTRY_TYPE)) {
// we found a collection that accepts GIF, so post one
final ClientEntry m1 = col.createEntry();
m1.setTitle("Test post");
final 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;
final List<Category> entryCats = new ArrayList<Category>();
for (int i = 0; i < col.getCategories().size(); i++) {
final Categories cats = col.getCategories().get(i);
if (cats.isFixed() && fixedCat == null) {
final String scheme = cats.getScheme();
fixedCat = cats.getCategories().get(0);
if (fixedCat.getScheme() == null) {
fixedCat.setScheme(scheme);
}
entryCats.add(fixedCat);
} else if (!cats.isFixed() && unfixedCat == null) {
final String scheme = cats.getScheme();
unfixedCat = new Category();
unfixedCat.setScheme(scheme);
unfixedCat.setTerm("tagster");
entryCats.add(unfixedCat);
}
}
m1.setCategories(entryCats);
col.addEntry(m1);
// entry should now exist on server
final ClientEntry m2 = col.getEntry(m1.getEditURI());
assertNotNull(m2);
if (fixedCat != null) {
// we added a fixed category, let's make sure it's there
boolean foundCat = false;
for (final Object element : m2.getCategories()) {
final Category cat = (Category) element;
if (cat.getTerm().equals(fixedCat.getTerm())) {
foundCat = true;
}
}
assertTrue(foundCat);
}
if (unfixedCat != null) {
// we added an unfixed category, let's make sure it's there
boolean foundCat = false;
for (final Object element : m2.getCategories()) {
final Category cat = (Category) element;
if (cat.getTerm().equals(unfixedCat.getTerm())) {
foundCat = true;
}
}
assertTrue(foundCat);
}
// remove entry
m2.remove();
// fetching entry now should result in exception
boolean failed = false;
try {
col.getEntry(m1.getEditURI());
} catch (final ProponoException e) {
failed = true;
}
assertTrue(failed);
count++;
}
}
}
assertTrue(count > 0);
}
/**
* Post media entry to every media colletion avialable on server, then cleanup.
*/
public void testMediaPost() throws Exception {
assertNotNull(service);
assertTrue(!service.getWorkspaces().isEmpty());
int count = 0;
for (final Object element : service.getWorkspaces()) {
final ClientWorkspace space = (ClientWorkspace) element;
assertNotNull(space.getTitle());
for (final Object element2 : space.getCollections()) {
final ClientCollection col = (ClientCollection) element2;
if (col.accepts("image/gif")) {
// we found a collection that accepts GIF, so post one
final 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
final 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 (final 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; i++) { ClientMediaEntry m1 =
* col.createMediaEntry("duke"+count, "duke"+count, "image/gif", new
* FileInputStream("test/testdata/duke-wave-shadow.gif")); col.addEntry(m1); posted.add(m1); }
* int entryCount = 0; for (Iterator iter = col.getEntries(); iter.hasNext();) {
* ClientMediaEntry entry = (ClientMediaEntry) iter.next(); entryCount++; } for (Iterator delit
* = posted.iterator(); delit.hasNext();) { ClientEntry entry = (ClientEntry) delit.next();
* entry.remove(); } assertTrue(entryCount >= maxPagingEntries); count++; break; } } }
* assertTrue(count > 0); }
*/
}

View file

@ -0,0 +1,88 @@
/*
* 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.rometools.propono.atom.client;
import java.util.Iterator;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.junit.Ignore;
import com.rometools.rome.feed.atom.Content;
/**
* Simple APP test designed to run against Blogger.com.
*/
@Ignore
public class BloggerDotComTest extends TestCase {
private final String collectionURI = "http://www.blogger.com/feeds/BLOGID/posts/default";
// private final String atomServiceURI =
// "http://www.blogger.com/feeds/default/blogs?alt=atom-service";
private final String email = "EMAIL";
private final String password = "PASSWORD";
public BloggerDotComTest(final String testName) {
super(testName);
}
@Override
protected void setUp() throws Exception {
}
@Override
protected void tearDown() throws Exception {
}
public static Test suite() {
final 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 (final Iterator<ClientEntry> it = col.getEntries(); it.hasNext();) {
final ClientEntry entry = it.next();
assertNotNull(entry);
count++;
}
assertTrue(count > 0);
col = AtomClientFactory.getCollection(collectionURI, new GDataAuthStrategy(email, password, "blogger"));
final ClientEntry p1 = col.createEntry();
p1.setTitle("Propono post");
final Content c = new Content();
c.setValue("This is content from ROME Propono");
p1.setContent(c);
col.addEntry(p1);
final ClientEntry p2 = col.getEntry(p1.getEditURI());
assertNotNull(p2);
final ClientAtomService atomService = AtomClientFactory.getAtomService(collectionURI, new GDataAuthStrategy(email, password, "blogger"));
assertNotNull(atomService);
}
}

View file

@ -0,0 +1,148 @@
/*
* 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.rometools.propono.atom.common;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
/**
* Tests reading and writing of service document, no server needed.
*/
public class AtomServiceTest extends TestCase {
public AtomServiceTest(final String testName) {
super(testName);
}
@Override
protected void setUp() throws Exception {
}
@Override
protected void tearDown() throws Exception {
}
public static Test suite() {
final TestSuite suite = new TestSuite(AtomServiceTest.class);
return suite;
}
/**
* Test of documentToService method, of class AtomService.
*/
public void testDocumentToService() {
try {
// Load service document from disk
final SAXBuilder builder = new SAXBuilder();
final Document document = builder.build(this.getClass().getResourceAsStream("/servicedoc1.xml"));
assertNotNull(document);
final AtomService service = AtomService.documentToService(document);
int workspaceCount = 0;
// Verify that service contains expected workspaces, collections and categories
for (final Object element : service.getWorkspaces()) {
final Workspace space = (Workspace) element;
assertNotNull(space.getTitle());
workspaceCount++;
for (final Object element2 : space.getCollections()) {
final Collection col = (Collection) element2;
assertNotNull(col.getTitle());
assertNotNull(col.getHrefResolved());
int catCount = 0;
if (!col.getCategories().isEmpty()) {
for (final Object element3 : col.getCategories()) {
final Categories cats = (Categories) element3;
catCount += cats.getCategories().size();
assertTrue(catCount > 0);
}
}
}
}
assertTrue(workspaceCount > 0);
} catch (final Exception e) {
e.printStackTrace();
fail();
}
}
/**
* Test of documentToService method, of class AtomService.
*/
public void testServiceToDocument() {
try {
// Create service with workspace and collections
final AtomService service = new AtomService();
final Workspace workspace1 = new Workspace("workspace1", null);
final Workspace workspace2 = new Workspace("workspace1", null);
service.addWorkspace(workspace1);
service.addWorkspace(workspace2);
final Collection collection11 = new Collection("collection11", null, "http://example.com/app/col11");
final Collection collection12 = new Collection("collection12", null, "http://example.com/app/col12");
workspace1.addCollection(collection11);
workspace1.addCollection(collection12);
final Collection collection21 = new Collection("collection21", null, "http://example.com/app/col21");
final 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
final Document document = service.serviceToDocument();
// verify that JDOM document contains service, workspace and collection
assertEquals("service", document.getRootElement().getName());
int workspaceCount = 0;
for (final Object element : document.getRootElement().getChildren()) {
final Element elem = (Element) element;
if ("workspace".equals(elem.getName())) {
workspaceCount++;
}
boolean workspaceTitle = false;
int collectionCount = 0;
for (final Object element2 : elem.getChildren()) {
final Element colelem = (Element) element2;
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 (final Exception e) {
e.printStackTrace();
fail();
}
}
}

View file

@ -0,0 +1,69 @@
/*
* 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.rometools.propono.atom.common;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import junit.framework.TestCase;
/**
* Tests Collection class, no server needed.
*/
public class CollectionTest extends TestCase {
public CollectionTest(final String testName) {
super(testName);
}
@Override
protected void setUp() throws Exception {
}
@Override
protected void tearDown() throws Exception {
}
/**
* Test of accepts method, of class com.rometools.rome.propono.atom.common.Collection.
*/
public void testAccepts() {
final 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"));
final List<String> accepts = new ArrayList<String>();
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"));
}
}

View file

@ -0,0 +1,428 @@
/*
* 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.rometools.propono.atom.server;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static junit.framework.TestCase.assertTrue;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mortbay.http.HttpContext;
import org.mortbay.http.HttpServer;
import org.mortbay.http.SocketListener;
import org.mortbay.jetty.servlet.ServletHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.propono.atom.client.AtomClientFactory;
import com.rometools.propono.atom.client.BasicAuthStrategy;
import com.rometools.propono.atom.client.ClientAtomService;
import com.rometools.propono.atom.client.ClientCollection;
import com.rometools.propono.atom.client.ClientEntry;
import com.rometools.propono.atom.client.ClientMediaEntry;
import com.rometools.propono.atom.client.ClientWorkspace;
import com.rometools.propono.atom.common.Categories;
import com.rometools.propono.atom.common.Collection;
import com.rometools.propono.atom.common.Workspace;
import com.rometools.propono.utils.ProponoException;
import com.rometools.rome.feed.atom.Category;
import com.rometools.rome.feed.atom.Content;
/**
* Test Propono Atom Client against Atom Server via Jetty. Extends <code>AtomClientTest</code> to
* start Jetty server, run tests and then stop the Jetty server.
*/
public class AtomClientServerTest {
private static final Logger LOG = LoggerFactory.getLogger(AtomClientServerTest.class);
private HttpServer server = null;
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";
private static ClientAtomService service = null;
public String getEndpoint() {
return ENDPOINT;
}
public String getUsername() {
return USERNAME;
}
public String getPassword() {
return PASSWORD;
}
protected HttpServer getServer() {
return server;
}
@Before
public void setUpClass() throws Exception {
LOG.info("---------------------------------------------");
LOG.info("Starting Jetty");
LOG.info("---------------------------------------------");
setupServer();
final HttpContext context = createContext();
final ServletHandler servlets = createServletHandler();
context.addHandler(servlets);
server.addContext(context);
server.start();
service = AtomClientFactory.getAtomService(getEndpoint(), new BasicAuthStrategy(getUsername(), getPassword()));
}
@After
public void tearDownClass() throws Exception {
if (server != null) {
LOG.info("Stoping Jetty");
server.stop();
server.destroy();
server = null;
}
}
private void setupServer() throws InterruptedException {
// Create the server
if (server != null) {
server.stop();
server = null;
}
server = new HttpServer();
// Create a port listener
final SocketListener listener = new SocketListener();
listener.setPort(TESTPORT);
server.addListener(listener);
}
private ServletHandler createServletHandler() {
System.setProperty("com.rometools.propono.atom.server.AtomHandlerFactory", "com.rometools.propono.atom.server.TestAtomHandlerFactory");
final ServletHandler servlets = new ServletHandler();
servlets.addServlet("app", "/app/*", "com.rometools.propono.atom.server.AtomServlet");
return servlets;
}
private HttpContext createContext() {
final HttpContext context = new HttpContext();
context.setContextPath("/rome/*");
return context;
}
/**
* Tests that server has introspection doc with at least one workspace.
*/
@Test
public void testGetAtomService() throws Exception {
assertNotNull(service);
assertTrue(!service.getWorkspaces().isEmpty());
for (final Workspace workspace : service.getWorkspaces()) {
final ClientWorkspace space = (ClientWorkspace) workspace;
assertNotNull(space.getTitle());
LOG.debug("Workspace: {}", space.getTitle());
for (final Object element : space.getCollections()) {
final ClientCollection col = (ClientCollection) element;
LOG.debug(" Collection: {} Accepts: {}", col.getTitle(), 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.
*/
@Test
public void testSimpleEntryPostAndRemove() throws Exception {
assertNotNull(service);
assertTrue(!service.getWorkspaces().isEmpty());
int count = 0;
for (final Object element : service.getWorkspaces()) {
final ClientWorkspace space = (ClientWorkspace) element;
assertNotNull(space.getTitle());
for (final Object element2 : space.getCollections()) {
final ClientCollection col = (ClientCollection) element2;
if (col.accepts(Collection.ENTRY_TYPE)) {
// we found a collection that accepts entries, so post one
final ClientEntry m1 = col.createEntry();
m1.setTitle("Test post");
final 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
final 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 (final 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.
*/
@Test
public void testSimpleEntryPostUpdateAndRemove() throws Exception {
assertNotNull(service);
assertTrue(!service.getWorkspaces().isEmpty());
int count = 0;
for (final Object element : service.getWorkspaces()) {
final ClientWorkspace space = (ClientWorkspace) element;
assertNotNull(space.getTitle());
for (final Object element2 : space.getCollections()) {
final ClientCollection col = (ClientCollection) element2;
if (col.accepts(Collection.ENTRY_TYPE)) {
// we found a collection that accepts entries, so post one
final ClientEntry m1 = col.createEntry();
m1.setTitle(col.getTitle() + ": Test post");
final 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
final ClientEntry m2 = col.getEntry(m1.getEditURI());
assertNotNull(m2);
m2.setTitle(col.getTitle() + ": Updated title");
m2.update();
// entry should now be updated on server
final ClientEntry m3 = 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 (final ProponoException e) {
failed = true;
}
assertTrue(failed);
count++;
}
}
}
assertTrue(count > 0);
}
@Test
public void testFindWorkspace() throws Exception {
assertNotNull(service);
final ClientWorkspace ws = (ClientWorkspace) service.findWorkspace("adminblog");
if (ws != null) {
final ClientCollection col = (ClientCollection) ws.findCollection(null, "entry");
final ClientEntry entry = col.createEntry();
entry.setTitle("NPE on submitting order query");
entry.setContent("This is a <b>bad</b> one!", Content.HTML);
col.addEntry(entry);
// entry should now exist on server
final ClientEntry saved = 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 (final 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.
*/
@Test
public void testEntryPostWithCategories() throws Exception {
assertNotNull(service);
assertTrue(!service.getWorkspaces().isEmpty());
int count = 0;
for (final Object element2 : service.getWorkspaces()) {
final ClientWorkspace space = (ClientWorkspace) element2;
assertNotNull(space.getTitle());
for (final Object element3 : space.getCollections()) {
final ClientCollection col = (ClientCollection) element3;
if (col.accepts(Collection.ENTRY_TYPE)) {
// we found a collection that accepts GIF, so post one
final ClientEntry m1 = col.createEntry();
m1.setTitle("Test post");
final 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;
final List<Category> entryCats = new ArrayList<Category>();
for (int i = 0; i < col.getCategories().size(); i++) {
final Categories cats = col.getCategories().get(i);
if (cats.isFixed() && fixedCat == null) {
final String scheme = cats.getScheme();
fixedCat = cats.getCategories().get(0);
if (fixedCat.getScheme() == null) {
fixedCat.setScheme(scheme);
}
entryCats.add(fixedCat);
} else if (!cats.isFixed() && unfixedCat == null) {
final String scheme = cats.getScheme();
unfixedCat = new Category();
unfixedCat.setScheme(scheme);
unfixedCat.setTerm("tagster");
entryCats.add(unfixedCat);
}
}
m1.setCategories(entryCats);
col.addEntry(m1);
// entry should now exist on server
final ClientEntry m2 = col.getEntry(m1.getEditURI());
assertNotNull(m2);
if (fixedCat != null) {
// we added a fixed category, let's make sure it's there
boolean foundCat = false;
for (final Object element : m2.getCategories()) {
final Category cat = (Category) element;
if (cat.getTerm().equals(fixedCat.getTerm())) {
foundCat = true;
}
}
assertTrue(foundCat);
}
if (unfixedCat != null) {
// we added an unfixed category, let's make sure it's there
boolean foundCat = false;
for (final Object element : m2.getCategories()) {
final Category cat = (Category) element;
if (cat.getTerm().equals(unfixedCat.getTerm())) {
foundCat = true;
}
}
assertTrue(foundCat);
}
// remove entry
m2.remove();
// fetching entry now should result in exception
boolean failed = false;
try {
col.getEntry(m1.getEditURI());
} catch (final ProponoException e) {
failed = true;
}
assertTrue(failed);
count++;
}
}
}
assertTrue(count > 0);
}
/**
* Post media entry to every media colletion avialable on server, then cleanup.
*/
public void testMediaPost() throws Exception {
assertNotNull(service);
assertTrue(!service.getWorkspaces().isEmpty());
int count = 0;
for (final Object element : service.getWorkspaces()) {
final ClientWorkspace space = (ClientWorkspace) element;
assertNotNull(space.getTitle());
for (final Object element2 : space.getCollections()) {
final ClientCollection col = (ClientCollection) element2;
if (col.accepts("image/gif")) {
// we found a collection that accepts GIF, so post one
final 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
final 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 (final ProponoException e) {
failed = true;
}
assertTrue(failed);
count++;
}
}
}
assertTrue(count > 0);
}
}

View file

@ -0,0 +1,28 @@
/*
* 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.rometools.propono.atom.server;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestAtomHandlerFactory extends AtomHandlerFactory {
@Override
public AtomHandler newAtomHandler(final HttpServletRequest req, final HttpServletResponse res) {
return new TestAtomHandlerImpl(req, "target/testuploaddir");
}
}

Some files were not shown because too many files have changed in this diff Show more