/*
* Weblounge: Web Content Management System
* Copyright (c) 2003 - 2011 The Weblounge Team
* http://entwinemedia.com/weblounge
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package ch.entwine.weblounge.common.impl.content;
import ch.entwine.weblounge.common.Times;
import ch.entwine.weblounge.common.impl.security.UserImpl;
import ch.entwine.weblounge.common.impl.util.WebloungeDateFormat;
import ch.entwine.weblounge.common.impl.util.xml.XPathHelper;
import ch.entwine.weblounge.common.security.User;
import org.w3c.dom.Node;
import java.text.ParseException;
import java.util.Date;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
/**
* The publishing context contains information about when an object will be
* published and thus be openly accessible. It can be used by
* <code>Publishable</code> objects as the backing implementation.
* <p>
* The publishing context adds additional means of specifying and querying
* publisher and publishing start and end date. It also allows for easy
* serialization and deserialization of <code>Publishable</code> data.
* <p>
* Following is an example of the data structure that the publishing context is
* able to handle:
*
* <pre>
* <published>
* <user id="john" realm="testland">John Doe</user>
* <from>2009/11/06 08:52:52 GMT</from>
* <to>2010/11/06 23:59:59 GMT</to>
* </published>
* </pre>
*
* @see ch.entwine.weblounge.common.content.Publishable
*/
public class PublishingContext implements Cloneable {
/** Publisher */
protected User publisher = null;
/** Start date */
protected Date startDate = null;
/** End date */
protected Date endDate = null;
/**
* Creates a default publishing context with a publishing start date of
* <code>now</code> (or <code>new Date()</code>, for that matter).
*/
public PublishingContext() {
}
/**
* Creates a default publishing context with a publishing start date of
* <code>now</code> (or <code>new Date()</code>, for that matter) and
* <code>publisher</code> as the publishing user.
*
* @param publisher
* the user publishing the content
*/
public PublishingContext(User publisher) {
this.publisher = publisher;
startDate = cutOffMillis(new Date());
}
/**
* Creates a default publishing context with the given publishing start and
* end date and <code>publisher</code> as the publishing user.
*
* @param publisher
* the user publishing the content
* @param startDate
* publication start date
* @param endDate
* publication end date
*/
public PublishingContext(User publisher, Date startDate, Date endDate) {
this.publisher = publisher;
this.startDate = cutOffMillis(startDate);
this.endDate = cutOffMillis(endDate);
}
/**
* Cut off the milliseconds from the date.
*
* @param date
* with milliseconds
* @return date without milliseconds
*/
private Date cutOffMillis(Date date) {
if (date == null)
return null;
long time = date.getTime() / 1000L;
return new Date(time * 1000L);
}
/**
* Sets the publisher, start and end date of this context. Throws an
* {@link IllegalArgumentException} if <code>startDate</code> is the same as
* or after <code>endDate</code>.
*
* @param publisher
* the publishing user
* @param startDate
* the publishing start date
* @param endDate
* the publishing end date
* @throws IllegalArgumentException
* if the start date is after the end date
*/
public void setPublished(User publisher, Date startDate, Date endDate)
throws IllegalStateException {
if (endDate != null && startDate.after(endDate))
throw new IllegalArgumentException("Start date is after end date");
this.publisher = publisher;
this.startDate = cutOffMillis(startDate);
this.endDate = cutOffMillis(endDate);
}
/**
* Sets the user that published the object.
*
* @param user
* the publisher
*/
public void setPublisher(User user) {
publisher = user;
}
/**
* Returns the user that published the object.
*
* @return the publisher
*/
public User getPublisher() {
return publisher;
}
/**
* Sets the publishing start date.
* <p>
* Pass <code>null</code> to set no start date at all, meaning that the object
* will be published until the end date is reached. If <code>startDate</code>
* is after the current end date, an {@link IllegalArgumentException} is
* thrown.
*
* @param startDate
* the start date
* @throw IllegalArgumentException if <code>startDate</code> is after the
* current end date
*/
public void setPublishFrom(Date startDate) {
if (startDate != null && endDate != null && startDate.after(endDate))
throw new IllegalArgumentException("Start date is after end date");
this.startDate = cutOffMillis(startDate);
}
/**
* Returns the publishing start date. A value of <code>null</code> means that
* this object will be published until the publishing end date is reached.
*
* @return the publishing end date
* @see #getPublishTo()
*/
public Date getPublishFrom() {
return startDate;
}
/**
* Sets the publishing end date.
* <p>
* Pass <code>null</code> to set no end date at all, meaning that the object
* will be published forever starting on the current start date. If
* <code>endDate</code> is before the current start date, an
* {@link IllegalArgumentException} is thrown.
*
* @param endDate
* the end date
* @throw IllegalArgumentException if <code>endDate</code> is before the
* current start date
*/
public void setPublishTo(Date endDate) {
if (startDate != null && endDate != null && endDate.before(startDate))
throw new IllegalArgumentException("End date is before start date");
this.endDate = cutOffMillis(endDate);
}
/**
* Returns the publishing end date. A value of <code>null</code> means that
* this object will be published forever.
*
* @return the publishing end date
* @see #getPublishFrom()
*/
public Date getPublishTo() {
return endDate;
}
/**
* Returns <code>true</code> if the object is published with respect to the
* current date, that is <code>new Date()</code> is after the current start
* date and before the current end date.
*
* @return <code>true</code> if the object is currently published
*/
public boolean isPublished() {
return isPublished(new Date());
}
/**
* Returns <code>true</code> if the object is published with respect to
* <code>date</code>, that is <code>date</code> is after the current start
* date and before the current end date.
*
* @return <code>true</code> if the object is published on the indicated date
*/
public boolean isPublished(Date date) {
date = cutOffMillis(date);
if (date == null)
throw new IllegalArgumentException("Date cannot be null");
boolean checkFrom = startDate != null && startDate.before(date);
boolean checkTo = endDate == null || endDate.after(date);
return (checkFrom && checkTo);
}
/**
* Returns a copy of this publishing context.
*
* @see java.lang.Object#clone()
*/
public Object clone() throws CloneNotSupportedException {
PublishingContext ctxt = (PublishingContext) super.clone();
if (publisher != null)
ctxt.publisher = (User) publisher.clone();
if (startDate != null)
ctxt.startDate = (Date) startDate.clone();
if (endDate != null)
ctxt.endDate = (Date) endDate.clone();
return ctxt;
}
/**
* Initializes this context from an XML node that was generated using
* {@link #toXml()}.
* <p>
* To speed things up, you might consider using the second signature that uses
* an existing <code>XPath</code> instance instead of creating a new one.
*
* @param context
* the publish context node
* @throws IllegalStateException
* if the context cannot be parsed
* @see #fromXml(Node, XPath)
* @see #toXml()
*/
public static PublishingContext fromXml(Node context)
throws IllegalStateException {
XPath xpath = XPathFactory.newInstance().newXPath();
return fromXml(context, xpath);
}
/**
* Initializes this context from an XML node that was generated using
* {@link #toXml()}.
*
* @param context
* the publish context node
* @param xpathProcessor
* the xpath processor
* @throws IllegalStateException
* if the context cannot be parsed
* @see #toXml()
*/
public static PublishingContext fromXml(Node context, XPath xpathProcessor)
throws IllegalStateException {
// Look up the root node
Node contextRoot = XPathHelper.select(context, "/published", xpathProcessor);
if (contextRoot == null)
return null;
PublishingContext ctx = new PublishingContext();
// Publisher
Node publisher = XPathHelper.select(contextRoot, "/published/user", xpathProcessor);
if (publisher != null)
ctx.setPublisher(UserImpl.fromXml(publisher, xpathProcessor));
// Start date
String startDate = XPathHelper.valueOf(contextRoot, "/published/from", xpathProcessor);
if (startDate != null) {
try {
ctx.setPublishFrom(WebloungeDateFormat.parseStatic(startDate));
} catch (ParseException e) {
throw new IllegalStateException("The publishing start date '" + startDate + "' cannot be parsed", e);
}
}
// End date
String endDate = XPathHelper.valueOf(contextRoot, "/published/to", xpathProcessor);
if (endDate != null) {
try {
Date date = WebloungeDateFormat.parseStatic(endDate);
if (date.getTime() < Times.MAX_DATE)
ctx.setPublishTo(date);
} catch (ParseException e) {
throw new IllegalStateException("The publishing end date '" + endDate + "' cannot be parsed", e);
}
}
return ctx;
}
/**
* Returns an <code>XML</code> representation of the context, that will look
* similar to the following example:
*
* <pre>
* <published>
* <user id="john" realm="testland">John Doe</user>
* <from>2009/11/06 08:52:52 GMT</from>
* <to>2010/11/06 23:59:59 GMT</to>
* </published>
* </pre>
*
* Use {@link #fromXml(Node))} or {@link #fromXml(Node, XPath)} to create a
* <code>PublishingContext</code> from the serialized output of this method.
*
* @return the <code>XML</code> representation of the context
* @see #fromXml(Node)
* @see #fromXml(Node, XPath)
*/
public String toXml() {
if (publisher == null || startDate == null)
return "";
StringBuffer b = new StringBuffer();
b.append("<published>");
// User
if (publisher != null) {
b.append("<user");
// id
b.append(" id=\"");
b.append(publisher.getLogin());
b.append("\"");
// realm
if (publisher.getRealm() != null) {
b.append(" realm=\"");
b.append(publisher.getRealm());
b.append("\"");
}
b.append(">");
// name
if (publisher.getName() != null) {
b.append("<![CDATA[").append(publisher.getName()).append("]]>");
}
b.append("</user>");
}
// Start date
if (startDate != null) {
b.append("<from>");
b.append(WebloungeDateFormat.formatStatic(startDate));
b.append("</from>");
}
// End date
if (endDate != null) {
b.append("<to>");
b.append(WebloungeDateFormat.formatStatic(endDate));
b.append("</to>");
}
b.append("</published>");
return b.toString();
}
}