577 lines
18 KiB
Java
Executable File
577 lines
18 KiB
Java
Executable File
/*
|
|
* 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.
|
|
*
|
|
* <p>For example, the URL "http://www.example.com/news/" can be broken down
|
|
* into:</p>
|
|
*
|
|
* <ul>
|
|
* <li>the protocol, which is <code><http://</code> </li>
|
|
* <li>the server, which is <code>www.example.com</code>. </li>
|
|
* <li>the hierarchy on the server, which is <code>/news/</code></li>
|
|
* </ul>
|
|
*
|
|
* <p>The hierarchy is delimited by slashes (/).</p>
|
|
*
|
|
* <ul>
|
|
* <li>The first "/" indicates the root <code>SiteNode</code>, the parent of
|
|
* all other site nodes on the system.</li>
|
|
*
|
|
* <li>The next token ("news/") indicates a <code>SiteNode</code> that is a
|
|
* child of the root <code>SiteNode</code> with a name of "news."</li>
|
|
* </ul>
|
|
*
|
|
* @version 1.0
|
|
* @version $Id: SiteNode.java 287 2005-02-22 00:29:02Z sskracic $
|
|
* @since ACS 5.0
|
|
* @deprecated Refactor to use {@link com.arsdigita.web.Application} instead.
|
|
*/
|
|
public class SiteNode extends ACSObject {
|
|
|
|
private static final String s_typeName = "com.arsdigita.kernel.SiteNode";
|
|
private static final Logger s_log =
|
|
Logger.getLogger(SiteNode.class.getName());
|
|
|
|
private static final Cache s_cache = new Cache();
|
|
|
|
protected String getBaseDataObjectType() {
|
|
return s_typeName;
|
|
}
|
|
|
|
private static SiteNode s_rootSiteNode;
|
|
|
|
// This holds the class that maintains the hierarchy for the site node
|
|
private HierarchyDenormalization m_hierarchy;
|
|
|
|
/**
|
|
* Default constructor. The contained <code>DataObject</code> is
|
|
* initialized with a new <code>DataObject</code> with an
|
|
* <code>ObjectType</code> 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, <em>not</em> 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, <em>not</em> 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, <em>not</em> 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 <code>true</code> if this SiteNode can have children;
|
|
* <code>false</code> otherwise.
|
|
*/
|
|
public boolean isDirectory() {
|
|
return ((Boolean) (get("isDirectory"))).booleanValue();
|
|
}
|
|
|
|
/**
|
|
* @return <code>true</code> if the SiteNode supports patterns;
|
|
* <code>false</code> 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;
|
|
}
|
|
}
|
|
}
|