/* * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. * * This library 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.1 of * the License, or (at your option) any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package com.arsdigita.kernel; import com.arsdigita.domain.DataObjectNotFoundException; import com.arsdigita.persistence.DataAssociation; import com.arsdigita.persistence.DataCollection; import com.arsdigita.persistence.DataObject; import com.arsdigita.persistence.OID; import com.arsdigita.persistence.Session; import com.arsdigita.persistence.SessionManager; import com.arsdigita.util.HierarchyDenormalization; import com.arsdigita.web.PathMapCache; import java.math.BigDecimal; import java.util.Locale; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; /** * A SiteNode represents a part of the URL hierarchy on a server. Each instance * of a SiteNode may be mapped to an application instance for the purpose of * providing access to that application. * *
For example, the URL "http://www.example.com/news/" can be broken down * into:
* * www.example.com. /news/The hierarchy is delimited by slashes (/).
* *SiteNode, the parent of
* all other site nodes on the system.SiteNode that is a
* child of the root SiteNode with a name of "news."DataObject is
* initialized with a new DataObject with an
* ObjectType of "SiteNode".
*
* @see com.arsdigita.persistence.DataObject
* @see com.arsdigita.persistence.metadata.ObjectType
* @deprecated see above
*/
public SiteNode() {
super(s_typeName);
}
/**
* Creates a new DomainObject instance to encapsulate a given
* data object.
*
* @param dataObject the data object to encapsulate in the new domain
* object
* @see com.arsdigita.persistence.Session#retrieve(String)
* @deprecated see above
*/
public SiteNode(DataObject dataObject) {
super(dataObject);
}
protected void initialize() {
super.initialize();
if (isNew()) {
set("isDirectory", Boolean.TRUE);
set("isPattern", Boolean.FALSE);
}
m_hierarchy = new HierarchyDenormalization
("com.arsdigita.kernel.updateSiteNodeDescendants", this, "url") {};
}
/**
* Retrieves the SiteNode with the specified ID.
*
* @param id a SiteNode ID
* @exception DataObjectNotFoundException if the ID does not match
* a SiteNode in the system.
* @deprecated see above
*/
public SiteNode(BigDecimal id) throws DataObjectNotFoundException {
super(new OID(s_typeName, id));
}
/**
* Retrieves a SiteNode that corresponds to the specified OID.
*
* @param oid the OID for the retrieved instance
* @see com.arsdigita.domain.DomainObject#DomainObject(OID)
* @see com.arsdigita.persistence.OID
* @deprecated see above
*/
public SiteNode(OID oid) throws DataObjectNotFoundException {
super(oid);
}
/**
* Gets the URL of the site note.
* @return the URL of the site node, not the URL requested by the
* user!
*
* @param req Servlet request. This is needed to get the context
* path, in case the servlet is not mounted at /.
*/
public String getURL(HttpServletRequest req) {
return getURL(req==null ? (String) null : req.getContextPath());
}
public String getUrl(HttpServletRequest req) {
return getURL(req);
}
/**
* @return the URL of the site node, not the URL requested by the
* user!
*
* @param ContextPath in case the servlet is not mounted at /
*/
public String getURL(String contextPath) {
if (contextPath != null) {
return contextPath + getURLNoContext();
} else {
return getURLNoContext();
}
}
public String getUrl(String contextPath) {
return getURL(contextPath);
}
public String getURL() {
return getURLNoContext();
}
private void setURL() {
s_log.debug("Setting url: " + getURLFromParent());
set("url", getURLFromParent());
}
private String getURLFromParent() {
SiteNode parent = getParent();
if (parent != null) {
return parent.getURL() + getName() + "/";
} else {
return getName() + "/";
}
}
/**
* @return the URL of the site node, not the URL
* requested by the user!
*/
public String getURLNoContext() {
String value = (String)get("url");
if (value == null) {
return "/";
} else {
return value;
}
}
public BigDecimal getNodeId() {
return (BigDecimal) get("id");
}
public void setName(String name) {
set("name", name);
setURL();
}
public void setParent(SiteNode siteNode) {
// Should throw an Exception if parent is not a directory.
if (siteNode == null) {
set("parent", null);
} else {
if (((Boolean) (siteNode.get("isDirectory"))).booleanValue()) {
setAssociation("parent", siteNode);
}
}
setURL();
}
public String getName() {
String name = (String) get("name");
if (name == null) {
return "";
} else {
return name;
}
}
/**
* Returns a display name for this site node.
*
* @see ACSObject#getDisplayName()
*/
public String getDisplayName() {
return getURL();
}
/**
* @return true if this SiteNode can have children;
* false otherwise.
*/
public boolean isDirectory() {
return ((Boolean) (get("isDirectory"))).booleanValue();
}
/**
* @return true if the SiteNode supports patterns;
* false otherwise.
* @deprecated
*/
public boolean isPattern() {
return ((Boolean)(get("isPattern"))).booleanValue();
}
public PackageInstance getPackageInstance() {
DataObject dataObject = (DataObject)get("mountedObject");
if (dataObject != null) {
return new PackageInstance(dataObject);
} else {
if (s_log.isDebugEnabled()) {
s_log.debug("No package mounted; returning null");
}
return null;
}
}
public void mountPackage(PackageInstance pkg) {
unMountPackage();
setAssociation("mountedObject", pkg);
PackageEventListener pels[] = pkg.getType().getListeners();
for (int i = 0; i < pels.length; i++) {
PackageEventListener listener = pels[i];
listener.onMount(this, pkg);
}
}
public void unMountPackage() {
PackageInstance pkg = getPackageInstance();
if (pkg != null) {
PackageEventListener pels[] = pkg.getType().getListeners();
for (int i = 0; i < pels.length; i++) {
PackageEventListener listener = pels[i];
listener.onUnmount(this, pkg);
}
set("mountedObject", null);
}
}
public SiteNode getParent() {
DataObject dataObject = (DataObject)get("parent");
if (dataObject == null || dataObject.get("id") == null) {
return null;
} else {
return new SiteNode(dataObject);
}
}
public SiteNodeCollection getChildren() {
DataAssociation childAssociation = (DataAssociation) get("children");
return new SiteNodeCollection(childAssociation);
}
public static SiteNode getRootSiteNode() {
// cache the site node statically
// note lack of synchronization. worst case:
// two different threads concurrently query for the root
// site node and one gets garbage collected. BFD.
if (s_rootSiteNode == null || !s_rootSiteNode.isValid()) {
// Retrieve read-only copy in new transaction context to
// guarantee validity.
Session ssn = SessionManager.getSession();
DataCollection rootSiteNode = SessionManager.getSession().retrieve
("com.arsdigita.kernel.SiteNode");
rootSiteNode.addEqualsFilter("name", null);
rootSiteNode.addEqualsFilter("parent.id", null);
if (rootSiteNode.next()) {
try {
DataObject dobj =
ssn.retrieve(new OID(s_typeName,
rootSiteNode.get("id")));
dobj.disconnect();
s_rootSiteNode = new SiteNode(dobj);
} finally {
rootSiteNode.close();
}
} else {
throw new DataObjectNotFoundException
("getRootSiteNode: Root site node not found.");
}
rootSiteNode.close();
}
return s_rootSiteNode;
}
/**
* for testing, it is necessary to remove all statically
* cached site nodes. Call this method AFTER creating
* any new site nodes as part of your unit test's
* setup method
*/
public static void repopulateCache() {
s_cache.refresh();
}
/**
* Finds the site node corresponding to the largest portion
* of the specified path. The path must begin with '/'.
* Any trailing slashes are ignored.
*
* @param path an absolute path to find the site node of
* @param readOnly if true, we return a read-only site node
* from our cache, which may be disconnected from a db session and
* can't be modified or deleted. (This is the desired behavior
* the majority of the time.)
*
* @return the site node corresponding to the path, or
* null if no matching site node can be found.
* @throws DataObjectNotFoundException if the path does not start
* with a slash (probably the wrong behavior) or if the
* RootSiteNode was requested but couldn't be found (also
* probably wrong).
*/
public static SiteNode getSiteNode(final String path, boolean readOnly)
throws DataObjectNotFoundException {
if (s_log.isDebugEnabled()) {
s_log.debug("Finding the site node for path '" + path + "'");
}
final SiteNode siteNode = s_cache.getNode(path);
if (siteNode == null) {
throw new DataObjectNotFoundException
("RootSiteNode not available." +
" The data model is not properly loaded.");
}
return readOnly ? siteNode : new SiteNode(siteNode.getOID());
}
/**
* Finds the site node corresponding to the largest portion
* of the specified path. The path must begin with '/'.
* Any trailing slashes are ignored.
*
* @param path an absolute path to find the site node of
* @return the site node corresponding to the path, or
* null if no matching site node can be found.
* @throws DataObjectNotFoundException if the path does not start
* with a slash (probably the wrong behavior) or if the
* RootSiteNode was requested but couldn't be found (also
* probably wrong).
*/
public static SiteNode getSiteNode(String path)
throws DataObjectNotFoundException {
return getSiteNode(path, false);
}
public static SiteNode createSiteNode(String name) {
return createSiteNode(name, getRootSiteNode());
}
/**
* Overrides the default save method. If we've changed the
* URL of this site node (either by changing its name or its parent),
* we need to also update the URL for all descendants of this site node.
*/
protected void beforeSave() {
if (isPropertyModified("url") || isNew()) {
s_cache.scheduleRefresh();
}
super.beforeSave();
}
public void afterDelete() {
s_cache.scheduleRefresh();
}
public static SiteNode createSiteNode(String name, SiteNode parent) {
SiteNode siteNode = new SiteNode();
siteNode.setName(name);
siteNode.setParent(parent);
siteNode.save();
return siteNode;
}
public String toString() {
return "[url: " + getURL() + "]";
}
public void addStylesheet(Stylesheet sheet) {
sheet.addToAssociation((DataAssociation)get("defaultStyle"));
}
/**
*
* @param locale
* @param outputType
* @return
* @deprecated without direct replacement. It is designed to work with
* {@link com.arsdigita.templating.LegacyStylesheetResolver} which is
* replaced by {@link com.arsdigita.templating.PatternStylesheetResolver}.
* So thes method is just not used anymore. (pboy)
*/
public Stylesheet[] getStylesheets(Locale locale, String outputType) {
return StyleAssociation
.getStylesheets(get("defaultStyle"), locale, outputType);
}
/**
*
* @param locale
* @param outputType
* @return
* @deprecated without direct replacement. It is design wo work with
* {@link com.arsdigita.templating.LegacyStylesheetResolver} which is
* replaced by {@link com.arsdigita.templating.PatternStylesheetResolver}.
* So this method is just not used anymore. (pboy)
*/
public Stylesheet getStylesheet(Locale locale, String outputType) {
return StyleAssociation
.getStylesheet(get("defaultStyle"), locale, outputType);
}
public void removeStylesheet(Stylesheet sheet) {
sheet.removeFromAssociation((DataAssociation)get("defaultStyle"));
}
// the sole purpose of this class is to make site nodes hash the same across
// multiple server instances.
private static class SiteNodeWrapper {
final SiteNode m_node;
SiteNodeWrapper(SiteNode node) {
if (node==null) { throw new NullPointerException("node"); }
m_node = node;
}
public boolean equals(Object obj) {
if (obj==null) { return false; }
SiteNodeWrapper snw = (SiteNodeWrapper) obj;
return m_node.getID().equals(snw.m_node.getID()) &&
m_node.getURL().equals(snw.m_node.getURL());
}
public int hashCode() {
return m_node.getID().hashCode() + m_node.getURL().hashCode();
}
public String toString() {
return m_node.toString();
}
}
// Caching of Site Nodes
// Stores the cached (url, siteNode) mappings.
private static class Cache extends PathMapCache {
public Cache() {
super("SiteNodeCache");
}
// implements the PathMapCache interface
public String normalize(String path) {
if ( path==null ) { throw new NullPointerException("path"); }
if ( !path.startsWith("/") ) {
throw new DataObjectNotFoundException
("The URL path specified must begin with a '/'.");
}
return path.endsWith("/") ? path : (path + "/");
}
// implements the PathMapCache interface
public Object retrieve(String path) {
DataCollection dc = SessionManager.getSession().retrieve
("com.arsdigita.kernel.SiteNode");
dc.addEqualsFilter("url", path);
SiteNode siteNode = null;
if (dc.next()) {
DataObject dobj = dc.getDataObject();
dobj.disconnect();
siteNode = new SiteNode(dobj);
if (dc.next()) {
s_log.error("More than one site node found for url " +
path + " ids: " + siteNode.getID() +
" and " + dc.get("id"));
}
dc.close();
}
return siteNode==null ? null : new SiteNodeWrapper(siteNode);
}
// implements the PathMapCache interface
public void refresh() {
clearAll();
}
void scheduleRefresh() {
super.refreshAfterCommit();
}
synchronized SiteNode getNode(String path) {
SiteNodeWrapper snw = (SiteNodeWrapper) super.get(path);
return snw.m_node;
}
}
}