/* * 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:

* * * *

The hierarchy is delimited by slashes (/).

* * * * @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 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; } } }