Set some additional headers to improve cache handling for multi lingual sites

git-svn-id: https://svn.libreccm.org/ccm/trunk@4911 8810af33-2d31-482b-a856-94f89814c4df
master
jensp 2017-08-18 19:57:44 +00:00
parent 3aea3f4425
commit c3a9839ae5
6 changed files with 697 additions and 463 deletions

View File

@ -27,12 +27,15 @@ import com.arsdigita.bebop.parameters.ParameterModel;
import com.arsdigita.bebop.parameters.StringParameter; import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.bebop.util.Traversal; import com.arsdigita.bebop.util.Traversal;
import com.arsdigita.developersupport.DeveloperSupport; import com.arsdigita.developersupport.DeveloperSupport;
import com.arsdigita.globalization.GlobalizationHelper;
import com.arsdigita.kernel.Kernel; import com.arsdigita.kernel.Kernel;
import com.arsdigita.profiler.Profiler; import com.arsdigita.profiler.Profiler;
import com.arsdigita.util.Assert; import com.arsdigita.util.Assert;
import com.arsdigita.util.SystemInformation; import com.arsdigita.util.SystemInformation;
import com.arsdigita.web.WebConfig;
import com.arsdigita.xml.Document; import com.arsdigita.xml.Document;
import com.arsdigita.xml.Element; import com.arsdigita.xml.Element;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.Collection; import java.util.Collection;
@ -45,21 +48,27 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
/** /**
* The top-level container for all Bebop components and containers. * The top-level container for all Bebop components and containers.
* *
* <UL> * <UL>
* <LI>Holds references to the components of a page.</LI> * <LI>Holds references to the components of a page.</LI>
* <LI>Provides methods for servicing requests and for notifying other components that a request for * <LI>Provides methods for servicing requests and for notifying other
* this page has been received through {@link ActionListener ActionListeners}.</LI> * components that a request for this page has been received through
* <LI>Tracks request parameters for stateful components, such as tabbed panes and sortable * {@link ActionListener ActionListeners}.</LI>
* tables.</LI> * <LI>Tracks request parameters for stateful components, such as tabbed panes
* and sortable tables.</LI>
* </UL> * </UL>
* *
* A typical <code>Page</code> may be created as follows: null <blockquote><pre><code> * A typical <code>Page</code> may be created as follows: null <blockquote><pre><code>
@ -85,14 +94,16 @@ public class Page extends SimpleComponent implements Container {
*/ */
private static final String DELIMITER = "."; private static final String DELIMITER = ".";
/** /**
* The prefix that gets prepended to all state variables. Components must not use variables * The prefix that gets prepended to all state variables. Components must
* starting with this prefix. This guarantees that the page state and variables individual * not use variables starting with this prefix. This guarantees that the
* components wish to pass do not interfere with each other. * page state and variables individual components wish to pass do not
* interfere with each other.
*/ */
private static final String COMPONENT_PREFIX = "bbp" + DELIMITER; private static final String COMPONENT_PREFIX = "bbp" + DELIMITER;
private static final String INTERNAL = COMPONENT_PREFIX; private static final String INTERNAL = COMPONENT_PREFIX;
/** /**
* The name of the special parameter that indicates which component has been selected. * The name of the special parameter that indicates which component has been
* selected.
*/ */
static final String SELECTED = INTERNAL + "s"; static final String SELECTED = INTERNAL + "s";
static final String CONTROL_EVENT = INTERNAL + "e"; static final String CONTROL_EVENT = INTERNAL + "e";
@ -109,14 +120,15 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* The name of the request parameter used for the visibility state of components stored in * The name of the request parameter used for the visibility state of
* m_invisible. * components stored in m_invisible.
*/ */
static final String INVISIBLE = INTERNAL + "i"; static final String INVISIBLE = INTERNAL + "i";
/** /**
* Map of stateful components (id --> Component) SortedMap used because component based hash for * Map of stateful components (id --> Component) SortedMap used because
* page is based on concatenation of component ids, and so need to guarantee that they are * component based hash for page is based on concatenation of component ids,
* returned in the same order for the same page - cg. * and so need to guarantee that they are returned in the same order for the
* same page - cg.
*/ */
private SortedMap m_componentMap; private SortedMap m_componentMap;
private List m_components; private List m_components;
@ -132,32 +144,35 @@ public class Page extends SimpleComponent implements Container {
private List m_actionListeners; private List m_actionListeners;
private List m_requestListeners; private List m_requestListeners;
/** /**
* The title of the page to be added in the head of HTML output. The title is wrapped in a Label * The title of the page to be added in the head of HTML output. The title
* to allow developers to add PrintListeners to dynamically change the value of the title. * is wrapped in a Label to allow developers to add PrintListeners to
* dynamically change the value of the title.
*/ */
private Label m_title; private Label m_title;
/** /**
* Stores the actual title for the current request. The title may be generated with a * Stores the actual title for the current request. The title may be
* PrintListener of the m_title Label. * generated with a PrintListener of the m_title Label.
*/ */
private RequestLocal m_currentTitle; private RequestLocal m_currentTitle;
/** /**
* A list of all the client-side stylesheets. The elements of the list are of type * A list of all the client-side stylesheets. The elements of the list are
* Page.Stylesheet, defined at the end of this file. * of type Page.Stylesheet, defined at the end of this file.
*/ */
private List m_clientStylesheets; private List m_clientStylesheets;
private StringParameter m_selected; private StringParameter m_selected;
private StringParameter m_controlEvent; private StringParameter m_controlEvent;
private StringParameter m_controlValue; private StringParameter m_controlValue;
/** /**
* The default (initial) visibility of components. The encoding is identical to that for * The default (initial) visibility of components. The encoding is identical
* PageState.m_invisible. * to that for PageState.m_invisible.
* *
* This variable is package-friendly since it needs to be accessed by PageState. * This variable is package-friendly since it needs to be accessed by
* PageState.
*/ */
protected BitSet m_invisible; protected BitSet m_invisible;
/** /**
* The PageErrorDisplay component that will display page state validation errors on this page * The PageErrorDisplay component that will display page state validation
* errors on this page
*/ */
private Component m_errorDisplay; private Component m_errorDisplay;
/** /**
@ -165,39 +180,41 @@ public class Page extends SimpleComponent implements Container {
*/ */
private boolean m_finished = false; private boolean m_finished = false;
/** /**
* indicates whether pageState.stateAsURL() should export the entire state for this page, or * indicates whether pageState.stateAsURL() should export the entire state
* whether it should only export the control event as a URL and use the HttpSession for the rest * for this page, or whether it should only export the control event as a
* of the page state. * URL and use the HttpSession for the rest of the page state.
*/ */
private boolean m_useHttpSession = false; private boolean m_useHttpSession = false;
/** /**
* Returns <code>true</code> if this page should export state through the HttpSession instead of * Returns <code>true</code> if this page should export state through the
* the URL query string. * HttpSession instead of the URL query string.
* <P> * <P>
* If this returns <code>true</code>, then PageState.stateAsURL() will only export the control * If this returns <code>true</code>, then PageState.stateAsURL() will only
* event as a URL query string. If this returns <code>false</code>, then stateAsURL() will * export the control event as a URL query string. If this returns
* export the entire page state. * <code>false</code>, then stateAsURL() will export the entire page state.
* *
* @see PageState#stateAsURL * @see PageState#stateAsURL
* *
* @return <code>true</code> if this page should export state through the HttpSession; * @return <code>true</code> if this page should export state through the
* <code>false</code> if it should export using the URL query string. * HttpSession; <code>false</code> if it should export using the URL
* query string.
*/ */
public boolean isUsingHttpSession() { public boolean isUsingHttpSession() {
return m_useHttpSession; return m_useHttpSession;
} }
/** /**
* Indicates to this page whether it should export its entire state to subsequent requests * Indicates to this page whether it should export its entire state to
* through the URL query string, or if it should use the HttpSession instead and only use the * subsequent requests through the URL query string, or if it should use the
* URL query string for the control event. * HttpSession instead and only use the URL query string for the control
* event.
* *
* @see PageState#stateAsURL * @see PageState#stateAsURL
* *
* @param b <code>true</code> if PageState.stateAsURL() will export only the control event as a * @param b <code>true</code> if PageState.stateAsURL() will export only the
* URL query string. <code>false</code> if stateAsURL() will export the entire page * control event as a URL query string. <code>false</code> if
* state. * stateAsURL() will export the entire page state.
*/ */
public void setUsingHttpSession(boolean b) { public void setUsingHttpSession(boolean b) {
m_useHttpSession = b; m_useHttpSession = b;
@ -273,7 +290,8 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Creates an empty page with the specified title and implicit BoxPanel container. * Creates an empty page with the specified title and implicit BoxPanel
* container.
* *
* @param title title for this page * @param title title for this page
*/ */
@ -284,7 +302,8 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Creates an empty page with the specified title and implicit BoxPanel container. * Creates an empty page with the specified title and implicit BoxPanel
* container.
* *
* @param title title for this page * @param title title for this page
*/ */
@ -304,12 +323,13 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Adds a component with the specified layout constraints to this container. Layout constraints * Adds a component with the specified layout constraints to this container.
* are defined in each layout container as static ints. To specify multiple constraints, use * Layout constraints are defined in each layout container as static ints.
* bitwise OR. * To specify multiple constraints, use bitwise OR.
* *
* @param c component to add to this container * @param c component to add to this container
* @param constraints layout constraints (a bitwise OR of static ints in the particular layout) * @param constraints layout constraints (a bitwise OR of static ints in the
* particular layout)
*/ */
@Override @Override
public void add(Component c, int constraints) { public void add(Component c, int constraints) {
@ -317,18 +337,20 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Returns <code>true</code> if this list contains the specified element. More formally, returns * Returns <code>true</code> if this list contains the specified element.
* <code>true</code> if and only if this list contains at least one element e such that (o==null * More formally, returns <code>true</code> if and only if this list
* ? e==null : o.equals(e)). * contains at least one element e such that (o==null ? e==null :
* o.equals(e)).
* <P> * <P>
* This method returns <code>true</code> only if the component has been directly added to this * This method returns <code>true</code> only if the component has been
* container. If this container contains another container that contains this component, this * directly added to this container. If this container contains another
* method returns <code>false</code>. * container that contains this component, this method returns
* <code>false</code>.
* *
* @param o element whose presence in this container is to be tested * @param o element whose presence in this container is to be tested
* *
* @return <code>true</code> if this Container contains the specified component directly; * @return <code>true</code> if this Container contains the specified
* <code>false</code> otherwise. * component directly; <code>false</code> otherwise.
*/ */
@Override @Override
public boolean contains(Object o) { public boolean contains(Object o) {
@ -336,9 +358,10 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Returns the component at the specified position. Each call to the add method increments the * Returns the component at the specified position. Each call to the add
* index. Since the user has no control over the index of added components (other than counting * method increments the index. Since the user has no control over the index
* each call to add), this method should be used in conjunction with indexOf. * of added components (other than counting each call to add), this method
* should be used in conjunction with indexOf.
* *
* @param index the index of the item to be retrieved from this Container * @param index the index of the item to be retrieved from this Container
* *
@ -354,12 +377,11 @@ public class Page extends SimpleComponent implements Container {
* *
* @param c component to search for * @param c component to search for
* *
* @return the index in this list of the first occurrence of the specified element, or -1 if * @return the index in this list of the first occurrence of the specified
* this list does not contain this element. * element, or -1 if this list does not contain this element.
* *
* @pre c != null * @pre c != null
* @post contains(c) implies (return >= 0) && (return < size()) * @post contains(c) implies (return >= 0) && (return < size()) @pos
* @pos
* t !contains(c) implies return == -1 * t !contains(c) implies return == -1
*/ */
@Override @Override
@ -370,8 +392,8 @@ public class Page extends SimpleComponent implements Container {
/** /**
* Returns <code>true</code> if the container contains no components. * Returns <code>true</code> if the container contains no components.
* *
* @return <code>true</code> if this container contains no components; <code>false</code> * @return <code>true</code> if this container contains no components;
* otherwise. * <code>false</code> otherwise.
*/ */
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
@ -379,8 +401,9 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Returns the number of elements in this container. This does not recursively count the * Returns the number of elements in this container. This does not
* components that are indirectly contained in this container. * recursively count the components that are indirectly contained in this
* container.
* *
* @return the number of components directly in this container. * @return the number of components directly in this container.
*/ */
@ -395,7 +418,8 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Returns the panel that the <code>Page</code> uses for rendering its components. * Returns the panel that the <code>Page</code> uses for rendering its
* components.
* *
* @return the panel. * @return the panel.
*/ */
@ -404,8 +428,9 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Set the Container used for rendering components on this page. Caution should be used with * Set the Container used for rendering components on this page. Caution
* this function, as the existing container is simply overwritten. * should be used with this function, as the existing container is simply
* overwritten.
* *
* @param c * @param c
* *
@ -456,15 +481,16 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Sets the {@link Component} that will display the validation errors in the current * Sets the {@link Component} that will display the validation errors in the
* {@link PageState}. Any validation error in the <code>PageState</code> will cause the * current {@link PageState}. Any validation error in the
* <code>Page</code> to completely ignore all other components and render only the error display * <code>PageState</code> will cause the <code>Page</code> to completely
* component. * ignore all other components and render only the error display component.
* <p> * <p>
* By default, a {@link PageErrorDisplay} component is used to display the validation errors. * By default, a {@link PageErrorDisplay} component is used to display the
* validation errors.
* *
* @param c the component that will display the validation errors in the current * @param c the component that will display the validation errors in the
* <code>PageState</code> * current <code>PageState</code>
*/ */
public final void setErrorDisplay(Component c) { public final void setErrorDisplay(Component c) {
Assert.isUnlocked(this); Assert.isUnlocked(this);
@ -472,32 +498,35 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Gets the {@link Component} that will display the validation errors in the current * Gets the {@link Component} that will display the validation errors in the
* {@link PageState}. Any validation error in the <code>PageState</code> will cause the * current {@link PageState}. Any validation error in the
* <code>Page</code> to completely ignore all other components and render only the error display * <code>PageState</code> will cause the <code>Page</code> to completely
* component. * ignore all other components and render only the error display component.
* <p> * <p>
* By default, a {@link PageErrorDisplay} component is used to display the validation errors. * By default, a {@link PageErrorDisplay} component is used to display the
* validation errors.
* *
* @return the component that will display the validation errors in the current * @return the component that will display the validation errors in the
* <code>PageState</code>. * current <code>PageState</code>.
*/ */
public final Component getErrorDisplay() { public final Component getErrorDisplay() {
return m_errorDisplay; return m_errorDisplay;
} }
/** /**
* Adds a client-side stylesheet that should be used in HTML output. Arbitrarily many * Adds a client-side stylesheet that should be used in HTML output.
* client-side stylesheets can be added with this method. To use a CSS stylesheet, call * Arbitrarily many client-side stylesheets can be added with this method.
* something like <code>setStyleSheet("style.css", "text/css")</code>. * To use a CSS stylesheet, call something like
* <code>setStyleSheet("style.css", "text/css")</code>.
* *
* <p> * <p>
* These values will ultimately wind up in a <tt>&lt;link&gt;</tt> * These values will ultimately wind up in a <tt>&lt;link&gt;</tt>
* tag in the head of the HTML page. * tag in the head of the HTML page.
* *
* <p> * <p>
* Note that the stylesheet set with this call has nothing to do with the XSLT stylesheet * Note that the stylesheet set with this call has nothing to do with the
* (transformer) that is applied to the XML generated from this page! * XSLT stylesheet (transformer) that is applied to the XML generated from
* this page!
* *
* @param styleSheetURI the location of the stylesheet * @param styleSheetURI the location of the stylesheet
* @param mimeType the MIME type of the stylesheet, usually * @param mimeType the MIME type of the stylesheet, usually
@ -510,13 +539,14 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Adds a global state parameter to this page. Global parameters are values that need to be * Adds a global state parameter to this page. Global parameters are values
* preserved between requests, but that have no special connection to any of the components on * that need to be preserved between requests, but that have no special
* the page. For a page that displays details about an item, a global parameter would be used to * connection to any of the components on the page. For a page that displays
* identify the item. * details about an item, a global parameter would be used to identify the
* item.
* *
* If the parameter was previously added as a component state parameter, its name is unmangled * If the parameter was previously added as a component state parameter, its
* and stays unmangled. * name is unmangled and stays unmangled.
* *
* @see #addComponentStateParam * @see #addComponentStateParam
* *
@ -532,8 +562,8 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Constructs the top nodes of the DOM or JDOM tree. Used by generateXML(PageState, Document) * Constructs the top nodes of the DOM or JDOM tree. Used by
* below. * generateXML(PageState, Document) below.
* <p> * <p>
* Generates DOM fragment: * Generates DOM fragment:
* <pre> * <pre>
@ -543,7 +573,8 @@ public class Page extends SimpleComponent implements Container {
* ... page content gnerated by children ... * ... page content gnerated by children ...
* &lt;/bebop:page></pre> The content of the <tt>&lt;title&gt;</tt> * &lt;/bebop:page></pre> The content of the <tt>&lt;title&gt;</tt>
* element can be set by calling {@link #setTitle setTitle}. The * element can be set by calling {@link #setTitle setTitle}. The
* <tt>&lt;stylesheet&gt;</tt> element will only be present if a stylesheet has been set with {@link * <tt>&lt;stylesheet&gt;</tt> element will only be present if a stylesheet
* has been set with {@link
* #setStyleSheet setStyleSheet}. * #setStyleSheet setStyleSheet}.
* *
* @param ps the page state for the current page * @param ps the page state for the current page
@ -573,8 +604,9 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Constructs a DOM or JDOM tree with all components on the page. The tree represents the page * Constructs a DOM or JDOM tree with all components on the page. The tree
* that results from the {@link javax.servlet.http.HttpServletRequest} kept in the * represents the page that results from the
* {@link javax.servlet.http.HttpServletRequest} kept in the
* <code>state</code>. * <code>state</code>.
* *
* @param state the page state produced by {@link #process} * @param state the page state produced by {@link #process}
@ -603,10 +635,33 @@ public class Page extends SimpleComponent implements Container {
if (Kernel.getConfig().isDebugEnabled() && debugStructure(state. if (Kernel.getConfig().isDebugEnabled() && debugStructure(state.
getRequest())) { getRequest())) {
Element structure = page.newChildElement("bebop:structure", BEBOP_XML_NS); Element structure = page.newChildElement("bebop:structure",
BEBOP_XML_NS);
showStructure(state, structure); showStructure(state, structure);
} }
final HttpServletRequest request = state.getRequest();
final HttpServletResponse response = state.getResponse();
if (response.isCommitted()) {
s_log.warn("Response already committed!!!");
}
final WebConfig webConfig = WebConfig.getInstanceOf();
if (webConfig.getVaryHeaders() != null
&& !webConfig.getVaryHeaders().isEmpty()) {
response.addHeader("Vary", webConfig.getVaryHeaders());
}
response.addHeader("Content-Language",
GlobalizationHelper.getNegotiatedLocale().toString());
final HttpSession session = request.getSession();
if (session != null && session.getAttribute(
GlobalizationHelper.LANG_PARAM) != null) {
response.addCookie(new Cookie(
GlobalizationHelper.LANG_PARAM,
(String) session.getAttribute(
GlobalizationHelper.LANG_PARAM)));
}
} }
private static boolean debugStructure(HttpServletRequest req) { private static boolean debugStructure(HttpServletRequest req) {
@ -623,11 +678,11 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Creates a PageState object and processes it by calling the respond method on the selected * Creates a PageState object and processes it by calling the respond method
* component. Processes a request by notifying the component from which the process originated * on the selected component. Processes a request by notifying the component
* and {@link #fireActionEvent * from which the process originated and {@link #fireActionEvent
* broadcasts} an {@link ActionEvent} to all the listeners that registered with * broadcasts} an {@link ActionEvent} to all the listeners that registered
* {@link #addActionListener addActionListener}. * with {@link #addActionListener addActionListener}.
* *
* @see #generateXML(PageState,Document) generateXML * @see #generateXML(PageState,Document) generateXML
* *
@ -656,8 +711,8 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Processes the supplied PageState object according to this PageModel. Calls the respond method * Processes the supplied PageState object according to this PageModel.
* on the selected Bebop component. * Calls the respond method on the selected Bebop component.
*/ */
public void process(PageState state) throws ServletException { public void process(PageState state) throws ServletException {
Assert.isLocked(this); Assert.isLocked(this);
@ -689,11 +744,13 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Builds a DOM Document from the current request state by doing a depth-first tree walk on the * Builds a DOM Document from the current request state by doing a
* current set of components in this Page, calling generateXML on each. Does NOT do the * depth-first tree walk on the current set of components in this Page,
* rendering. If the HTTP response has already been committed, does not build the XML document. * calling generateXML on each. Does NOT do the rendering. If the HTTP
* response has already been committed, does not build the XML document.
* *
* @return a DOM ready for rendering, or null if the response has already been committed. * @return a DOM ready for rendering, or null if the response has already
* been committed.
* *
* @post res.isCommitted() == (return == null) * @post res.isCommitted() == (return == null)
*/ */
@ -725,8 +782,8 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Finishes building the page. The tree of components is traversed and each component is told to * Finishes building the page. The tree of components is traversed and each
* add its state parameters to the page's state model. * component is told to add its state parameters to the page's state model.
* *
* @pre ! isLocked() * @pre ! isLocked()
*/ */
@ -760,7 +817,8 @@ public class Page extends SimpleComponent implements Container {
* Locks the page and all its components against further modifications. * Locks the page and all its components against further modifications.
* *
* <p> * <p>
* Locking a page helps in finding mistakes that result from modifying a page's structure.</P> * Locking a page helps in finding mistakes that result from modifying a
* page's structure.</P>
*/ */
@Override @Override
public void lock() { public void lock() {
@ -788,8 +846,8 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Registers a listener that is notified whenever a request to this page is made, after the * Registers a listener that is notified whenever a request to this page is
* selected component has had a chance to respond. * made, after the selected component has had a chance to respond.
* *
* @pre l != null * @pre l != null
* @pre ! isLocked() * @pre ! isLocked()
@ -811,8 +869,8 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Registers a listener that is notified whenever a request to this page is made, before the * Registers a listener that is notified whenever a request to this page is
* selected component has had a chance to respond. * made, before the selected component has had a chance to respond.
* *
* @pre l != null * @pre l != null
* @pre ! isLocked() * @pre ! isLocked()
@ -836,9 +894,9 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Broadcasts an {@link ActionEvent} to all registered listeners. The source of the event is * Broadcasts an {@link ActionEvent} to all registered listeners. The source
* this page, and the state recorded in the event is the one resulting from processing the * of the event is this page, and the state recorded in the event is the one
* current request. * resulting from processing the current request.
* *
* @param the state for this event * @param the state for this event
* *
@ -863,9 +921,9 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Broadcasts a {@link RequestEvent} to all registered listeners. The source of the event is * Broadcasts a {@link RequestEvent} to all registered listeners. The source
* this page, and the state recorded in the event is the one resulting from processing the * of the event is this page, and the state recorded in the event is the one
* current request. * resulting from processing the current request.
* *
* @param state the state for this event * @param state the state for this event
* *
@ -890,9 +948,9 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Export page generator information if set. The m_pageGenerator is a HashMap containing the * Export page generator information if set. The m_pageGenerator is a
* information as key value. In general this should include generator name and generator * HashMap containing the information as key value. In general this should
* version. * include generator name and generator version.
* *
* @param page parent element - should be bebeop:page * @param page parent element - should be bebeop:page
* *
@ -901,7 +959,8 @@ public class Page extends SimpleComponent implements Container {
final protected void exportSystemInformation(Element page) { final protected void exportSystemInformation(Element page) {
SystemInformation sysInfo = SystemInformation.getInstance(); SystemInformation sysInfo = SystemInformation.getInstance();
if (!sysInfo.isEmpty()) { if (!sysInfo.isEmpty()) {
Element gen = page.newChildElement("bebop:systemInformation", BEBOP_XML_NS); Element gen = page.newChildElement("bebop:systemInformation",
BEBOP_XML_NS);
Iterator<Map.Entry<String, String>> keyValues = sysInfo.iterator(); Iterator<Map.Entry<String, String>> keyValues = sysInfo.iterator();
while (keyValues.hasNext()) { while (keyValues.hasNext()) {
@ -944,7 +1003,8 @@ public class Page extends SimpleComponent implements Container {
if (!stateContains(c)) { if (!stateContains(c)) {
if (c == null) { if (c == null) {
s_log.error("c is null"); s_log.error("c is null");
} /*else { }
/*else {
s_log.error("c: " + c.toString()); s_log.error("c: " + c.toString());
}*/ }*/
@ -963,10 +1023,12 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Registers a state parameter for a component. It is permissible to register the same state * Registers a state parameter for a component. It is permissible to
* parameter several times, from the same or different components. The name of the parameter * register the same state parameter several times, from the same or
* will be changed to ensure that it won't clash with any other component's parameter. If the * different components. The name of the parameter will be changed to ensure
* parameter is added more than once, the name is only changed the first time it is added. * that it won't clash with any other component's parameter. If the
* parameter is added more than once, the name is only changed the first
* time it is added.
* *
* @param c the component to register the parameter for * @param c the component to register the parameter for
* @param p the state parameter to register * @param p the state parameter to register
@ -986,8 +1048,9 @@ public class Page extends SimpleComponent implements Container {
} }
if (!m_stateModel.containsFormParam(p)) { if (!m_stateModel.containsFormParam(p)) {
String name = parameterName(c, p.getName()); String name = parameterName(c, p.getName());
s_log.debug(String.format("Setting name of parameter to add to '%s'", s_log.debug(String
name)); .format("Setting name of parameter to add to '%s'",
name));
p.setName(name); p.setName(name);
m_stateModel.addFormParam(p); m_stateModel.addFormParam(p);
@ -1009,17 +1072,17 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Gets the state index of a component. This is the number assigned to the component in the * Gets the state index of a component. This is the number assigned to the
* register traveral * component in the register traveral
* *
* @param c the component to search for * @param c the component to search for
* *
* @return the index in this list of the first occurrence of the specified element, or -1 if * @return the index in this list of the first occurrence of the specified
* this list does not contain this element. * element, or -1 if this list does not contain this element.
* *
* @pre c != null * @pre c != null
* @post contains(c) implies (return >= 0) && (return < size()) @pos * @post contains(c) implies (return >= 0) && (return < size()) @pos t
* t !contains(c) implies return == -1 * !contains(c) implies return == -1
*/ */
public int stateIndex(Component c) { public int stateIndex(Component c) {
return m_components.indexOf(c); return m_components.indexOf(c);
@ -1046,8 +1109,7 @@ public class Page extends SimpleComponent implements Container {
/** /**
* Gets a page component by index. * Gets a page component by index.
* *
* @pre (i >= 0) && (i < size()) @pos * @pre (i >= 0) && (i < size()) @pos t return != null
* t return != null
*/ */
public Component getComponent(int i) { public Component getComponent(int i) {
return (Component) m_components.get(i); return (Component) m_components.get(i);
@ -1083,8 +1145,8 @@ public class Page extends SimpleComponent implements Container {
* *
* @param c a component contained in the page * @param c a component contained in the page
* *
* @return <code>true</code> if the component is visible by default; <code>false</code> * @return <code>true</code> if the component is visible by default;
* otherwise. * <code>false</code> otherwise.
* *
* @see #setVisibleDefault setVisibleDefault * @see #setVisibleDefault setVisibleDefault
* @see Component#setVisible Component.setVisible * @see Component#setVisible Component.setVisible
@ -1096,16 +1158,18 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Sets whether the specified component is visible by default. The default visibility is used * Sets whether the specified component is visible by default. The default
* when a page is displayed for the first time and on subsequent requests until the visibility * visibility is used when a page is displayed for the first time and on
* of a component is changed explicitly with {@link Component#setVisible * subsequent requests until the visibility of a component is changed
* explicitly with {@link Component#setVisible
* Component.setVisible}. * Component.setVisible}.
* *
* <p> * <p>
* When a component is first added to a page, it is visible. * When a component is first added to a page, it is visible.
* *
* @param c a component whose visibility is to be set * @param c a component whose visibility is to be set
* @param v <code>true</code> if the component is visible; <code>false</code> otherwise. * @param v <code>true</code> if the component is visible;
* <code>false</code> otherwise.
* *
* @see Component#setVisible Component.setVisible * @see Component#setVisible Component.setVisible
* @see Component#register Component.register * @see Component#register Component.register
@ -1123,7 +1187,8 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* The global name of the parameter <code>name</code> in the component <code>c</code>. * The global name of the parameter <code>name</code> in the component
* <code>c</code>.
*/ */
public String parameterName(Component c, String name) { public String parameterName(Component c, String name) {
if (c == null || !stateContains(c)) { if (c == null || !stateContains(c)) {
@ -1161,7 +1226,8 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* Return the prefix that is prepended to each component's state parameters to keep them unique. * Return the prefix that is prepended to each component's state parameters
* to keep them unique.
*/ */
private final String componentPrefix(Component c) { private final String componentPrefix(Component c) {
if (c == null) { if (c == null) {
@ -1219,10 +1285,12 @@ public class Page extends SimpleComponent implements Container {
selected.setText(sel); selected.setText(sel);
// Control event // Control event
Element eventName = state.newChildElement("bebop:eventName", BEBOP_XML_NS); Element eventName = state.newChildElement("bebop:eventName",
BEBOP_XML_NS);
eventName.addAttribute(NAME, m_controlEvent.getName()); eventName.addAttribute(NAME, m_controlEvent.getName());
eventName.setText(req.getParameter(m_controlEvent.getName())); eventName.setText(req.getParameter(m_controlEvent.getName()));
Element eventValue = state.newChildElement("bebop:eventValue", BEBOP_XML_NS); Element eventValue = state.newChildElement("bebop:eventValue",
BEBOP_XML_NS);
eventValue.addAttribute(NAME, m_controlValue.getName()); eventValue.addAttribute(NAME, m_controlValue.getName());
eventValue.setText(req.getParameter(m_controlValue.getName())); eventValue.setText(req.getParameter(m_controlValue.getName()));
@ -1231,7 +1299,8 @@ public class Page extends SimpleComponent implements Container {
for (Iterator ii = getStateModel().getParameters(); ii.hasNext();) { for (Iterator ii = getStateModel().getParameters(); ii.hasNext();) {
ParameterModel p = (ParameterModel) ii.next(); ParameterModel p = (ParameterModel) ii.next();
if (!p.getName().startsWith(COMPONENT_PREFIX)) { if (!p.getName().startsWith(COMPONENT_PREFIX)) {
Element param = globalState.newChildElement("bebop:param", BEBOP_XML_NS); Element param = globalState.newChildElement("bebop:param",
BEBOP_XML_NS);
param.addAttribute(NAME, p.getName()); param.addAttribute(NAME, p.getName());
param.setText(String.valueOf(s.getValue(p))); param.setText(String.valueOf(s.getValue(p)));
} }
@ -1260,11 +1329,13 @@ public class Page extends SimpleComponent implements Container {
continue; continue;
} }
Element param = parent.newChildElement("bebop:param", BEBOP_XML_NS); Element param = parent.newChildElement("bebop:param",
BEBOP_XML_NS);
param.addAttribute(NAME, unmangle(p.getName())); param.addAttribute(NAME, unmangle(p.getName()));
param.addAttribute("defaultValue", param.addAttribute("defaultValue",
String.valueOf(req.getParameter(p.getName()))); String.valueOf(req.getParameter(p.getName())));
param.addAttribute("currentValue", String.valueOf(s.getValue(p))); param
.addAttribute("currentValue", String.valueOf(s.getValue(p)));
} }
} }
for (Iterator i = c.children(); i.hasNext();) { for (Iterator i = c.children(); i.hasNext();) {
@ -1282,9 +1353,9 @@ public class Page extends SimpleComponent implements Container {
} }
/** /**
* return a string that represents an ordered list of component ids used on the page. For * return a string that represents an ordered list of component ids used on
* situations where only the components present is of importance, this may be used by * the page. For situations where only the components present is of
* implementations of hashCode & equals * importance, this may be used by implementations of hashCode & equals
* *
* @return * @return
*/ */
@ -1304,7 +1375,8 @@ public class Page extends SimpleComponent implements Container {
String componentId = (String) it.next(); String componentId = (String) it.next();
hashString.append(componentId); hashString.append(componentId);
} }
s_log.debug("Time to create hashCode for page: " + (new Date().getTime() - start. s_log.debug("Time to create hashCode for page: " + (new Date().getTime()
- start.
getTime())); getTime()));
return hashString.toString(); return hashString.toString();

View File

@ -21,7 +21,7 @@ import javax.servlet.http.HttpSession;
public class GlobalizationHelper { public class GlobalizationHelper {
public static final String LANG_INDEPENDENT = Kernel.getConfig().getLanguagesIndependentCode(); public static final String LANG_INDEPENDENT = Kernel.getConfig().getLanguagesIndependentCode();
private static final String LANG_PARAM = "lang"; public static final String LANG_PARAM = "lang";
// Don't instantiate // Don't instantiate
private GlobalizationHelper() { private GlobalizationHelper() {

View File

@ -23,6 +23,7 @@ import com.arsdigita.developersupport.DeveloperSupport;
import com.arsdigita.dispatcher.DispatcherHelper; import com.arsdigita.dispatcher.DispatcherHelper;
import com.arsdigita.dispatcher.RequestEvent; import com.arsdigita.dispatcher.RequestEvent;
import com.arsdigita.domain.DataObjectNotFoundException; import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.globalization.GlobalizationHelper;
import com.arsdigita.persistence.SessionManager; import com.arsdigita.persistence.SessionManager;
import com.arsdigita.persistence.TransactionContext; import com.arsdigita.persistence.TransactionContext;
import com.arsdigita.profiler.Profiler; import com.arsdigita.profiler.Profiler;
@ -40,23 +41,26 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
// NOTE // NOTE
// Combines and replaces the previous classes DispatcherServlet and BaseDispatcher // Combines and replaces the previous classes DispatcherServlet and BaseDispatcher
// Most of their code and their separation are abundant as old style applications // Most of their code and their separation are abundant as old style applications
// are no longer supported. // are no longer supported.
/** /**
* <p>The CCM main dispatcher. This servlet serves as the main servlet / main * <p>
* entry point (mapped to "/someprefix/*") for requests to any CCM webapp.</p> * The CCM main dispatcher. This servlet serves as the main servlet / main entry
* point (mapped to "/someprefix/*") for requests to any CCM webapp.</p>
* *
* <p>Upon finding an {@link com.arsdigita.web.Application application} at the * <p>
* Upon finding an {@link com.arsdigita.web.Application application} at the
* requested URL, this class sets a request attribute storing the ID of the * requested URL, this class sets a request attribute storing the ID of the
* application and forwards to the servlet associated with that application. * application and forwards to the servlet associated with that application. If
* If instead no application is found, a 404 response is generated.</p> * instead no application is found, a 404 response is generated.</p>
* *
* <p>This servlet has to be deployed using web.xml entries like these:</p> * <p>
* This servlet has to be deployed using web.xml entries like these:</p>
* *
* <blockquote><pre> * <blockquote><pre>
* &lt;servlet&gt; * &lt;servlet&gt;
@ -70,7 +74,8 @@ import org.apache.log4j.Logger;
* &lt;/servlet-mapping&gt; * &lt;/servlet-mapping&gt;
* </pre></blockquote> * </pre></blockquote>
* *
* <p>It's important to also edit the com.arsdigita.web.WebConfig m_servlet * <p>
* It's important to also edit the com.arsdigita.web.WebConfig m_servlet
* parameter to reflect where you've put your dispatcher.</p> * parameter to reflect where you've put your dispatcher.</p>
* *
* <blockquote><pre> * <blockquote><pre>
@ -84,39 +89,47 @@ import org.apache.log4j.Logger;
* </pre></blockquote> * </pre></blockquote>
* *
* @see com.arsdigita.web.BaseApplicationServlet * @see com.arsdigita.web.BaseApplicationServlet
* @author Justin Ross &lt;<a href="mailto:jross@redhat.com">jross@redhat.com</a>&gt; * @author Justin Ross
* @author Peter Boy &lt;<a href="mailto:pboy@barkhof.uni-bremen.de">Peter Boy</a>&gt; * &lt;<a href="mailto:jross@redhat.com">jross@redhat.com</a>&gt;
* @author Peter Boy &lt;<a href="mailto:pboy@barkhof.uni-bremen.de">Peter
* Boy</a>&gt;
* @version $Id: DispatcherServlet.java 738 2005-09-01 12:36:52Z sskracic $ * @version $Id: DispatcherServlet.java 738 2005-09-01 12:36:52Z sskracic $
*/ */
public class CCMDispatcherServlet extends BaseServlet { public class CCMDispatcherServlet extends BaseServlet {
/** Internal logger instance to faciliate debugging. Enable logging output /**
* by editing /WEB-INF/conf/log4j.properties int hte runtime environment * Internal logger instance to faciliate debugging. Enable logging output by
* and set com.arsdigita.web.CCMDispatcherServlet=DEBUG by uncommenting * editing /WEB-INF/conf/log4j.properties int hte runtime environment and
* or adding the line. */ * set com.arsdigita.web.CCMDispatcherServlet=DEBUG by uncommenting or
* adding the line.
*/
private static final Logger s_log = Logger.getLogger( private static final Logger s_log = Logger.getLogger(
CCMDispatcherServlet.class); CCMDispatcherServlet.class);
static final String DISPATCHED_ATTRIBUTE = static final String DISPATCHED_ATTRIBUTE = CCMDispatcherServlet.class
CCMDispatcherServlet.class.getName() + ".dispatched"; .getName() + ".dispatched";
/** Instance of the private Cache class */ /**
* Instance of the private Cache class
*/
private final static Cache s_cache = new Cache(); private final static Cache s_cache = new Cache();
/** String containing the web context path portion of the WEB application /**
* where this CCMDispatcherServlet is executed. (I.e. where the WEB-INF * String containing the web context path portion of the WEB application
* directory containing the web.xml configuring this CCMDispatcherServlet * where this CCMDispatcherServlet is executed. (I.e. where the WEB-INF
* is located in the servlet container webapps directory. * directory containing the web.xml configuring this CCMDispatcherServlet is
* */ * located in the servlet container webapps directory.
*
*/
private static String s_contextPath; private static String s_contextPath;
public final boolean isApplicationInCache(String path) { public final boolean isApplicationInCache(String path) {
return s_cache.isCached(path); return s_cache.isCached(path);
} }
/** /**
* Servlet initializer uses the extension point of parent class. * Servlet initializer uses the extension point of parent class.
*
* @throws ServletException * @throws ServletException
*/ */
@Override @Override
@ -126,59 +139,66 @@ public class CCMDispatcherServlet extends BaseServlet {
s_contextPath = servletContext.getContextPath(); s_contextPath = servletContext.getContextPath();
// For backwords compatibility reasons register the web application // For backwords compatibility reasons register the web application
// context of the Core (root) application als "/" // context of the Core (root) application als "/"
// Web.registerServletContext("/", // Web.registerServletContext("/",
// servletContext); // servletContext);
} }
/** /**
* Extends the standard service() method of the parent BaseServlet class. * Extends the standard service() method of the parent BaseServlet class.
* Looks up and identifies the web application addressed in the url and * Looks up and identifies the web application addressed in the url and
* forwards to that application's ApplicationServlet. * forwards to that application's ApplicationServlet. (new style legacy
* (new style legacy free) * free)
*
* @param servletRequest
* @param servletResponse
* *
* @param sreq
* @param sresp
* @throws ServletException * @throws ServletException
* @throws IOException * @throws IOException
*/ */
@Override @Override
protected void doService(final HttpServletRequest sreq, protected void doService(final HttpServletRequest servletRequest,
final HttpServletResponse sresp) final HttpServletResponse servletResponse)
throws ServletException, IOException { throws ServletException, IOException {
DeveloperSupport.requestStart(new RequestEvent(sreq, sresp, DeveloperSupport.requestStart(new RequestEvent(servletRequest,
servletResponse,
null, true, false)); null, true, false));
if (s_log.isDebugEnabled()) { if (s_log.isDebugEnabled()) {
s_log.debug("Dispatching request " + sreq.getRequestURI() + " [" + s_log.debug("Dispatching request " + servletRequest.getRequestURI()
sreq.getContextPath() + "," + + " ["
sreq.getServletPath() + "," + + servletRequest.getContextPath() + ","
sreq.getPathInfo() + "," + + servletRequest
sreq.getQueryString() + "]"); .getServletPath()
+ "," + servletRequest.getPathInfo() + ","
+ servletRequest
.getQueryString()
+ "]");
} }
DeveloperSupport.startStage("CCMDispatcherServlet.doService"); DeveloperSupport.startStage("CCMDispatcherServlet.doService");
final String path = servletRequest.getPathInfo();
final String path = sreq.getPathInfo();
if (requiresTrailingSlash(path)) { if (requiresTrailingSlash(path)) {
s_log.debug("The request URI needs a trailing slash; " + s_log.debug("The request URI needs a trailing slash; "
"redirecting"); + "redirecting");
final String prefix = DispatcherHelper.getDispatcherPrefix(sreq); final String prefix = DispatcherHelper.getDispatcherPrefix(
String uri = sreq.getRequestURI(); servletRequest);
String uri = servletRequest.getRequestURI();
if (prefix != null && prefix.trim().length() > 0) { if (prefix != null && prefix.trim().length() > 0) {
uri = prefix + uri; uri = prefix + uri;
} }
final String query = sreq.getQueryString(); final String query = servletRequest.getQueryString();
String url = null; String url = null;
if (query == null) { if (query == null) {
sresp.sendRedirect(sresp.encodeRedirectURL(uri + "/")); servletResponse.sendRedirect(servletResponse.encodeRedirectURL(
uri + "/"));
} else { } else {
sresp.sendRedirect servletResponse.sendRedirect(servletResponse.encodeRedirectURL(
(sresp.encodeRedirectURL(uri + "/?" + query)); uri + "/?" + query));
} }
// return true; // return true;
@ -186,41 +206,64 @@ public class CCMDispatcherServlet extends BaseServlet {
Assert.exists(path, String.class); Assert.exists(path, String.class);
s_log.debug("Storing the path elements of the current request as " + s_log.debug("Storing the path elements of the current request as "
"the original path elements"); + "the original path elements");
sreq.setAttribute(BaseServlet.REQUEST_URL_ATTRIBUTE, new URL(sreq)); servletRequest.setAttribute(BaseServlet.REQUEST_URL_ATTRIBUTE,
new URL(servletRequest));
if (s_log.isDebugEnabled()) { if (s_log.isDebugEnabled()) {
s_log.debug("Using path '" + path + "' to lookup application"); s_log.debug("Using path '" + path + "' to lookup application");
} }
DeveloperSupport.startStage("CCMDispatcherServlet.lookupApplicationSpec"); DeveloperSupport.startStage(
"CCMDispatcherServlet.lookupApplicationSpec");
final ApplicationSpec spec = lookupApplicationSpec(path); final ApplicationSpec spec = lookupApplicationSpec(path);
DeveloperSupport.endStage("CCMDispatcherServlet.lookupApplicationSpec"); DeveloperSupport.endStage(
"CCMDispatcherServlet.lookupApplicationSpec");
if (spec == null) { if (spec == null) {
s_log.debug("No application was found; doing nothing"); s_log.debug("No application was found; doing nothing");
// return false; // return false;
// we have to create a 404 page here! // we have to create a 404 page here!
String requestUri = sreq.getRequestURI(); // same as ctx.getRemainingURLPart() String requestUri = servletRequest.getRequestURI(); // same as ctx.getRemainingURLPart()
sresp.sendError(404, requestUri + " not found on this server."); servletResponse.sendError(404, requestUri
+ " not found on this server.");
} else { } else {
if (s_log.isDebugEnabled()) { if (s_log.isDebugEnabled()) {
s_log.debug("Found application " + spec.getAppID() + "; " + s_log.debug("Found application " + spec.getAppID() + "; "
"dispatching to its servlet"); + "dispatching to its servlet");
} }
sreq.setAttribute servletRequest.setAttribute(
(BaseApplicationServlet.APPLICATION_ID_ATTRIBUTE, BaseApplicationServlet.APPLICATION_ID_ATTRIBUTE,
spec.getAppID()); spec.getAppID());
sreq.setAttribute(DISPATCHED_ATTRIBUTE, Boolean.TRUE); servletRequest.setAttribute(DISPATCHED_ATTRIBUTE, Boolean.TRUE);
Profiler.startOp("APP"); // +spec.getAppID() XXX get app name? Profiler.startOp("APP"); // +spec.getAppID() XXX get app name?
forward(spec.getTypeContextPath(), spec.target(path), sreq, sresp);
// final WebConfig webConfig = WebConfig.getInstanceOf();
// if (webConfig.getVaryHeaders() != null
// && !webConfig.getVaryHeaders().isEmpty()) {
// servletResponse
// .addHeader("Vary", webConfig.getVaryHeaders());
// }
// final HttpSession session = servletRequest.getSession();
// if (session != null && session.getAttribute(
// GlobalizationHelper.LANG_PARAM) != null) {
//
// servletResponse.addCookie(new Cookie(
// GlobalizationHelper.LANG_PARAM,
// (String) session.getAttribute(
// GlobalizationHelper.LANG_PARAM)));
// }
forward(spec.getTypeContextPath(),
spec.target(path),
servletRequest,
servletResponse);
Profiler.stopOp("APP"); // +spec.getAppID() Profiler.stopOp("APP"); // +spec.getAppID()
// return true;
} }
} }
@ -229,10 +272,10 @@ public class CCMDispatcherServlet extends BaseServlet {
} }
/** /**
* *
* @param path * @param path
*
* @return * @return
*/ */
private boolean requiresTrailingSlash(final String path) { private boolean requiresTrailingSlash(final String path) {
@ -241,8 +284,8 @@ public class CCMDispatcherServlet extends BaseServlet {
} }
if (path == null) { if (path == null) {
s_log.debug("The path is null; the request needs a trailing " + s_log.debug("The path is null; the request needs a trailing "
"slash"); + "slash");
return true; return true;
} }
@ -252,13 +295,13 @@ public class CCMDispatcherServlet extends BaseServlet {
} }
if (path.lastIndexOf(".") < path.lastIndexOf("/")) { if (path.lastIndexOf(".") < path.lastIndexOf("/")) {
s_log.debug("The last fragment of the path has no '.', so we " + s_log.debug("The last fragment of the path has no '.', so we "
"assume a directory was requested; a trailing " + + "assume a directory was requested; a trailing "
"slash is required"); + "slash is required");
return true; return true;
} else { } else {
s_log.debug("The last fragment of the path appears to be a file " + s_log.debug("The last fragment of the path appears to be a file "
"name; no trailing slash is needed"); + "name; no trailing slash is needed");
return false; return false;
} }
} }
@ -269,6 +312,7 @@ public class CCMDispatcherServlet extends BaseServlet {
* @param target * @param target
* @param sreq * @param sreq
* @param sresp * @param sresp
*
* @throws ServletException * @throws ServletException
* @throws IOException * @throws IOException
*/ */
@ -276,7 +320,7 @@ public class CCMDispatcherServlet extends BaseServlet {
final String target, final String target,
final HttpServletRequest sreq, final HttpServletRequest sreq,
final HttpServletResponse sresp) final HttpServletResponse sresp)
throws ServletException, IOException { throws ServletException, IOException {
if (s_log.isDebugEnabled()) { if (s_log.isDebugEnabled()) {
s_log.debug("Forwarding by path to target '" + target + "'"); s_log.debug("Forwarding by path to target '" + target + "'");
@ -291,11 +335,11 @@ public class CCMDispatcherServlet extends BaseServlet {
// XXX We should pass servlet context down // XXX We should pass servlet context down
// final ServletContext context = Web.getServletContext(contextPath); // final ServletContext context = Web.getServletContext(contextPath);
final ServletContext context = Web.getServletContext() final ServletContext context = Web.getServletContext()
.getContext(contextPath); .getContext(contextPath);
if (s_log.isDebugEnabled()) { if (s_log.isDebugEnabled()) {
s_log.debug("From context " + Web.getServletContext() + s_log.debug("From context " + Web.getServletContext()
" to context " + context); + " to context " + context);
} }
final RequestDispatcher rd = context.getRequestDispatcher(target); final RequestDispatcher rd = context.getRequestDispatcher(target);
@ -310,15 +354,16 @@ public class CCMDispatcherServlet extends BaseServlet {
* @param rd * @param rd
* @param sreq * @param sreq
* @param sresp * @param sresp
*
* @throws ServletException * @throws ServletException
* @throws IOException * @throws IOException
*/ */
final void forward(final RequestDispatcher rd, final void forward(final RequestDispatcher rd,
HttpServletRequest sreq, HttpServletRequest sreq,
final HttpServletResponse sresp) final HttpServletResponse sresp)
throws ServletException, IOException { throws ServletException, IOException {
s_log.debug("Checking if this request needs to be forwarded or " + s_log.debug("Checking if this request needs to be forwarded or "
"included " + sreq); + "included " + sreq);
// Just in case sreq is wrapped in one of our own classes (spec. // Just in case sreq is wrapped in one of our own classes (spec.
// MultipartHttpServletRequest), return unwrapped request, otherwise // MultipartHttpServletRequest), return unwrapped request, otherwise
@ -326,32 +371,52 @@ public class CCMDispatcherServlet extends BaseServlet {
sreq = DispatcherHelper.restoreOriginalRequest(sreq); sreq = DispatcherHelper.restoreOriginalRequest(sreq);
if (sreq.getAttribute("javax.servlet.include.request_uri") == null) { if (sreq.getAttribute("javax.servlet.include.request_uri") == null) {
s_log.debug("The attribute javax.servlet.include.request_uri " + s_log.debug("The attribute javax.servlet.include.request_uri "
"is not set; forwarding " + sreq); + "is not set; forwarding " + sreq);
rd.forward(sreq, sresp); rd.forward(sreq, sresp);
} else { } else {
s_log.debug("The attribute javax.servlet.include.request_uri " + s_log.debug("The attribute javax.servlet.include.request_uri "
"is set; including " + sreq); + "is set; including " + sreq);
rd.include(sreq, sresp); rd.include(sreq, sresp);
} }
// if (sresp.isCommitted()) {
// s_log.warn("Response already committed!!!");
// }
// final WebConfig webConfig = WebConfig.getInstanceOf();
// if (webConfig.getVaryHeaders() != null
// && !webConfig.getVaryHeaders().isEmpty()) {
// sresp
// .addHeader("Vary", webConfig.getVaryHeaders());
// }
// final HttpSession session = sreq.getSession();
// if (session != null && session.getAttribute(
// GlobalizationHelper.LANG_PARAM) != null) {
//
// sresp.addCookie(new Cookie(
// GlobalizationHelper.LANG_PARAM,
// (String) session.getAttribute(
// GlobalizationHelper.LANG_PARAM)));
// }
} }
/** /**
* *
* @param path * @param path
*
* @return * @return
*/ */
private ApplicationSpec lookupApplicationSpec(final String path) { private ApplicationSpec lookupApplicationSpec(final String path) {
if (s_log.isDebugEnabled()) { if (s_log.isDebugEnabled()) {
s_log.debug("*** Starting application lookup for path '" + s_log.debug("*** Starting application lookup for path '" + path
path + "' ***"); + "' ***");
} }
ApplicationSpec spec = s_cache.getAppSpec(path); ApplicationSpec spec = s_cache.getAppSpec(path);
if ( spec == null ) { if (spec == null) {
s_log.debug("There's no application to be found"); s_log.debug("There's no application to be found");
} }
return spec; return spec;
@ -375,14 +440,12 @@ public class CCMDispatcherServlet extends BaseServlet {
@Override @Override
protected void doDestroy() { protected void doDestroy() {
} }
*/ */
/** /**
* Private class. * Private class.
*/ */
private static class ApplicationSpec { private static class ApplicationSpec {
private final BigDecimal m_id; private final BigDecimal m_id;
private final String m_instanceURI; private final String m_instanceURI;
private final String m_typeURI; private final String m_typeURI;
@ -393,11 +456,13 @@ public class CCMDispatcherServlet extends BaseServlet {
* @param app * @param app
*/ */
ApplicationSpec(Application app) { ApplicationSpec(Application app) {
if ( app == null ) { throw new NullPointerException("app"); } if (app == null) {
throw new NullPointerException("app");
}
m_id = app.getID(); m_id = app.getID();
m_instanceURI = app.getPath(); m_instanceURI = app.getPath();
m_typeURI = app.getServletPath(); m_typeURI = app.getServletPath();
m_typeContextPath = app.getContextPath(); m_typeContextPath = app.getContextPath();
if (Assert.isEnabled()) { if (Assert.isEnabled()) {
@ -412,18 +477,21 @@ public class CCMDispatcherServlet extends BaseServlet {
* *
* @return * @return
*/ */
BigDecimal getAppID() { return m_id; } BigDecimal getAppID() {
return m_id;
}
/** /**
* Provides the context the application is executing. Usually all CCM * Provides the context the application is executing. Usually all CCM
* applications will now execute in the same webapp context. The * applications will now execute in the same webapp context. The
* app.getContextPath() return "" in this case where an application is * app.getContextPath() return "" in this case where an application is
* executing in no specific context but CCM's default. * executing in no specific context but CCM's default.
*
* @return The context path of the application's url, "" in case of * @return The context path of the application's url, "" in case of
* executing in the ROOT context. * executing in the ROOT context.
*/ */
String getTypeContextPath() { String getTypeContextPath() {
if (m_typeContextPath.equals("") ) { if (m_typeContextPath.equals("")) {
// app is running in CCM's default context, determine the // app is running in CCM's default context, determine the
// actual one // actual one
return Web.getWebappContextPath(); return Web.getWebappContextPath();
@ -435,12 +503,13 @@ public class CCMDispatcherServlet extends BaseServlet {
/** /**
* *
* @param path * @param path
*
* @return * @return
*/ */
String target(final String path) { String target(final String path) {
if (s_log.isDebugEnabled()) { if (s_log.isDebugEnabled()) {
s_log.debug("Building the target path from the request path '" + s_log.debug("Building the target path from the request path '"
path + "' and the spec " + this); + path + "' and the spec " + this);
} }
final StringBuffer target = new StringBuffer(128); final StringBuffer target = new StringBuffer(128);
@ -462,17 +531,20 @@ public class CCMDispatcherServlet extends BaseServlet {
/** /**
* *
* @param obj * @param obj
*
* @return * @return
*/ */
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if ( obj==null ) { return false; } if (obj == null) {
return false;
}
ApplicationSpec other = (ApplicationSpec) obj; ApplicationSpec other = (ApplicationSpec) obj;
return m_id.equals(other.m_id) && return m_id.equals(other.m_id) && equal(m_instanceURI,
equal(m_instanceURI, other.m_instanceURI) && other.m_instanceURI)
equal(m_typeURI, other.m_typeURI) && && equal(m_typeURI, other.m_typeURI) && equal(
equal(m_typeContextPath, other.m_typeContextPath); m_typeContextPath, other.m_typeContextPath);
} }
@ -480,11 +552,16 @@ public class CCMDispatcherServlet extends BaseServlet {
* *
* @param s1 * @param s1
* @param s2 * @param s2
*
* @return * @return
*/ */
private boolean equal(String s1, String s2) { private boolean equal(String s1, String s2) {
if (s1==s2) { return true; } if (s1 == s2) {
if (s1==null) { return equal(s2, s1); } return true;
}
if (s1 == null) {
return equal(s2, s1);
}
return s1.equals(s2); return s1.equals(s2);
} }
@ -512,22 +589,22 @@ public class CCMDispatcherServlet extends BaseServlet {
sb.append("typeContextPath=").append(m_typeContextPath); sb.append("typeContextPath=").append(m_typeContextPath);
return sb.append("]").toString(); return sb.append("]").toString();
} }
} }
/** /**
* Private class Cache caches (path, AppSpec) mappings. * Private class Cache caches (path, AppSpec) mappings.
*/ */
private static class Cache extends PathMapCache { private static class Cache extends PathMapCache {
/** */
private static final ThreadLocal s_handleHere = new ThreadLocal() { private static final ThreadLocal s_handleHere = new ThreadLocal() {
@Override
protected Object initialValue() { @Override
return Boolean.FALSE; protected Object initialValue() {
} return Boolean.FALSE;
}; }
};
/** /**
* Constructor, just delegates to Super class * Constructor, just delegates to Super class
@ -539,23 +616,28 @@ public class CCMDispatcherServlet extends BaseServlet {
// implements the PathMapCache interface // implements the PathMapCache interface
@Override @Override
public String normalize(String path) { public String normalize(String path) {
if ( path==null ) { throw new NullPointerException("path"); } if (path == null) {
if ( !path.startsWith("/") ) { throw new NullPointerException("path");
throw new DataObjectNotFoundException
("The URL path specified must begin with a '/'.");
} }
return path.endsWith("/") ? if (!path.startsWith("/")) {
path : path.substring(0, path.lastIndexOf('/') + 1); throw new DataObjectNotFoundException(
"The URL path specified must begin with a '/'.");
}
return path.endsWith("/") ? path : path.substring(0, path
.lastIndexOf('/')
+ 1);
} }
// implements the PathMapCache interface // implements the PathMapCache interface
@Override @Override
public Object retrieve(String path) { public Object retrieve(String path) {
if ( "/".equals(path) ) { return null; } if ("/".equals(path)) {
return null;
}
final TransactionContext context = final TransactionContext context = SessionManager.getSession()
SessionManager.getSession().getTransactionContext(); .getTransactionContext();
if ( !context.inTxn() ) { if (!context.inTxn()) {
s_log.debug("Beginning transaction"); s_log.debug("Beginning transaction");
context.beginTxn(); context.beginTxn();
s_handleHere.set(Boolean.TRUE); s_handleHere.set(Boolean.TRUE);
@ -563,7 +645,7 @@ public class CCMDispatcherServlet extends BaseServlet {
Application app = Application.retrieveApplicationForPath(path); Application app = Application.retrieveApplicationForPath(path);
return app==null ? null : new ApplicationSpec(app); return app == null ? null : new ApplicationSpec(app);
} }
// implements the PathMapCache interface // implements the PathMapCache interface
@ -580,12 +662,14 @@ public class CCMDispatcherServlet extends BaseServlet {
try { try {
return (ApplicationSpec) super.get(path); return (ApplicationSpec) super.get(path);
} finally { } finally {
if ( s_handleHere.get() == Boolean.TRUE ) { if (s_handleHere.get() == Boolean.TRUE) {
s_handleHere.set(Boolean.FALSE); s_handleHere.set(Boolean.FALSE);
SessionManager.getSession().getTransactionContext(). SessionManager.getSession().getTransactionContext().
commitTxn(); commitTxn();
} }
} }
} }
} }
} }

View File

@ -39,8 +39,8 @@ import org.apache.log4j.Logger;
/** /**
* A record containing server-session scoped configuration properties. * A record containing server-session scoped configuration properties.
* *
* Accessors of this class may return null. Developers should take * Accessors of this class may return null. Developers should take care to trap
* care to trap null return values in their code. * null return values in their code.
* *
* @see com.arsdigita.web.Web * @see com.arsdigita.web.Web
* @author Justin Ross &lt;jross@redhat.com&gt; * @author Justin Ross &lt;jross@redhat.com&gt;
@ -48,20 +48,24 @@ import org.apache.log4j.Logger;
*/ */
public final class WebConfig extends AbstractConfig { public final class WebConfig extends AbstractConfig {
/** Internal logger instance to faciliate debugging. Enable logging output /**
* by editing /WEB-INF/conf/log4j.properties int the runtime environment * Internal logger instance to faciliate debugging. Enable logging output by
* and set com.arsdigita.web.WebConfig=DEBUG by uncommenting it */ * editing /WEB-INF/conf/log4j.properties int the runtime environment and
* set com.arsdigita.web.WebConfig=DEBUG by uncommenting it
*/
private static final Logger s_log = Logger.getLogger(WebConfig.class); private static final Logger s_log = Logger.getLogger(WebConfig.class);
/** Private Object to hold one's own instance to return to users. */ /**
private static WebConfig s_config ; * Private Object to hold one's own instance to return to users.
*/
private static WebConfig s_config;
/** /**
* Returns the singleton configuration record for the content section * Returns the singleton configuration record for the content section
* environment. * environment.
* *
* @return The <code>CMSConfig</code> record; it cannot be null * @return The <code>CMSConfig</code> record; it cannot be null
*/ */
public static synchronized WebConfig getInstanceOf() { public static synchronized WebConfig getInstanceOf() {
if (s_config == null) { if (s_config == null) {
s_config = new WebConfig(); s_config = new WebConfig();
@ -73,86 +77,117 @@ public final class WebConfig extends AbstractConfig {
// ///////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////
// Configuration parameter section // Configuration parameter section
// ///////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////
/**
* Determines what HTTP scheme prefix is used by default to generate URLs
* (either http od https)
*/
private final Parameter m_scheme = new DefaultSchemeParameter(
"waf.web.default_scheme",
Parameter.REQUIRED, "http");
/**
* Sets the name and port that users of a site will see in URLs generated by
* CCM for the site. This is a required parameter during installation, e.g.
* example.com:80
*/
private final Parameter m_server = new HttpHostParameter("waf.web.server");
/**
* Name and port that users of a site will see in secure URLs generated by
* CCM for the site. As an example: example.com:443
*/
private final Parameter m_secureServer = new HttpHostParameter(
"waf.web.secure_server",
Parameter.OPTIONAL, null);
/**
* The name of your website, for use in page footers for example. It's not
* necessarily the URL but rather a title, e.g. "House of HTML". If not
* specified set to the server's URL.
*/
private final Parameter m_site = new StringParameter("waf.web.site_name",
Parameter.OPTIONAL,
null) {
/** Determines what HTTP scheme prefix is used by default to generate URLs @Override
* (either http od https) */ public final Object getDefaultValue() {
private final Parameter m_scheme = new DefaultSchemeParameter final HttpHost host = getServer();
("waf.web.default_scheme", if (host == null) {
Parameter.REQUIRED, "http"); return null;
/** Sets the name and port that users of a site will see in URLs generated } else {
* by CCM for the site. This is a required parameter during installation, return host.toString();
* e.g. example.com:80 */ }
private final Parameter m_server = new HttpHostParameter }
("waf.web.server");
/** Name and port that users of a site will see in secure URLs generated
* by CCM for the site. As an example: example.com:443 */
private final Parameter m_secureServer = new HttpHostParameter
("waf.web.secure_server",
Parameter.OPTIONAL, null);
/** The name of your website, for use in page footers for example. It's
* not necessarily the URL but rather a title, e.g. "House of HTML".
* If not specified set to the server's URL. */
private final Parameter m_site= new StringParameter
("waf.web.site_name",
Parameter.OPTIONAL, null) { @Override
public final Object getDefaultValue() {
final HttpHost host = getServer();
if (host == null) {
return null;
} else {
return host.toString();
}
}
};
/** Sets the name and port of the machine on which the CCM instance is
* running. Used to fetch some resources by a local URL avoiding external
* internet traffic (and delay). If not specified set to the servers's
* name redirecting all traffic to external internet address. */
private final Parameter m_host = new HttpHostParameter
("waf.web.host",
Parameter.OPTIONAL, null) { @Override
public final Object getDefaultValue() {
return getServer();
}
};
/** List of URLs which accessed by insecure (normal HTTP) connection };
* produce a redirect to a HTTPS equivalent. List is comma separated. */ /**
private final Parameter m_secureRequired = new StringArrayParameter * Sets the name and port of the machine on which the CCM instance is
("waf.web.secure_required", Parameter.OPTIONAL, null); * running. Used to fetch some resources by a local URL avoiding external
/** List of URLs which accessed by secure (HTTPS) connection produce a * internet traffic (and delay). If not specified set to the servers's name
* redirect to a HTTP equivalent. List is comma separated. */ * redirecting all traffic to external internet address.
private final Parameter m_secureSwitchBack = new StringArrayParameter */
("waf.web.secure_switchback", Parameter.OPTIONAL, null); private final Parameter m_host = new HttpHostParameter("waf.web.host",
Parameter.OPTIONAL,
null) {
/** Dispatcher servlet path. It's the prefix to the main entry point for @Override
* any application request (CCMDispatcherServlet). By default /ccm */ public final Object getDefaultValue() {
private final Parameter m_servlet = new StringParameter return getServer();
("waf.web.dispatcher_servlet_path", Parameter.REQUIRED, "/ccm"); }
/** Specifies by name which implementation of ApplicationFileResolver is };
* used to dynamically resolve static files. By default
* DefaultApplicationFileResolver() is used. */
private final Parameter m_resolver = new SingletonParameter
("waf.web.application_file_resolver",
Parameter.OPTIONAL,
new DefaultApplicationFileResolver());
private final Parameter m_default_cache_policy = new CachePolicyParameter
("waf.web.cache_policy",
Parameter.OPTIONAL, null);
private final Parameter m_deactivate_cache_host_notifications = new BooleanParameter
("waf.web.deactivate_cache_host_notifications",
Parameter.OPTIONAL, Boolean.FALSE);
private final Parameter m_dynamic_host_provider = new StringParameter /**
("waf.web.dynamic_host_provider", * List of URLs which accessed by insecure (normal HTTP) connection produce
Parameter.OPTIONAL, ""); * a redirect to a HTTPS equivalent. List is comma separated.
*/
private final Parameter m_secureRequired = new StringArrayParameter(
"waf.web.secure_required", Parameter.OPTIONAL, null);
/**
* List of URLs which accessed by secure (HTTPS) connection produce a
* redirect to a HTTP equivalent. List is comma separated.
*/
private final Parameter m_secureSwitchBack = new StringArrayParameter(
"waf.web.secure_switchback", Parameter.OPTIONAL, null);
/** /**
* Constructor, but do NOT instantiate this class directly, use * Dispatcher servlet path. It's the prefix to the main entry point for any
* getInstanceOf() instead. (Singelton pattern!) * application request (CCMDispatcherServlet). By default /ccm
* */
*/ private final Parameter m_servlet = new StringParameter(
"waf.web.dispatcher_servlet_path", Parameter.REQUIRED, "/ccm");
/**
* Specifies by name which implementation of ApplicationFileResolver is used
* to dynamically resolve static files. By default
* DefaultApplicationFileResolver() is used.
*/
private final Parameter m_resolver = new SingletonParameter(
"waf.web.application_file_resolver",
Parameter.OPTIONAL,
new DefaultApplicationFileResolver());
private final Parameter m_default_cache_policy = new CachePolicyParameter(
"waf.web.cache_policy",
Parameter.OPTIONAL, null);
private final Parameter m_deactivate_cache_host_notifications
= new BooleanParameter(
"waf.web.deactivate_cache_host_notifications",
Parameter.OPTIONAL, Boolean.FALSE);
private final Parameter m_dynamic_host_provider = new StringParameter(
"waf.web.dynamic_host_provider",
Parameter.OPTIONAL, "");
/**
* Specifies the values set as {@code vary} HTTP header in the response.
*/
private final Parameter m_varyHeaders = new StringParameter(
"waf.web.vary_headers",
Parameter.OPTIONAL,
"accept-language");
/**
* Constructor, but do NOT instantiate this class directly, use
* getInstanceOf() instead. (Singleton pattern!)
*
*/
public WebConfig() { public WebConfig() {
register(m_scheme); register(m_scheme);
@ -167,6 +202,7 @@ public final class WebConfig extends AbstractConfig {
register(m_default_cache_policy); register(m_default_cache_policy);
register(m_deactivate_cache_host_notifications); register(m_deactivate_cache_host_notifications);
register(m_dynamic_host_provider); register(m_dynamic_host_provider);
register(m_varyHeaders);
loadInfo(); loadInfo();
} }
@ -177,8 +213,9 @@ public final class WebConfig extends AbstractConfig {
/** /**
* Provide the name and port that users of a site will see in URLs generated * Provide the name and port that users of a site will see in URLs generated
* by CCM for the site. (Value of parameter waf.web.server) * by CCM for the site. (Value of parameter waf.web.server) E.g.
* E.g. example.com:80 * example.com:80
*
* @return HttpHost object, contains public name & port of the server (site) * @return HttpHost object, contains public name & port of the server (site)
*/ */
public final HttpHost getServer() { public final HttpHost getServer() {
@ -190,9 +227,9 @@ public final class WebConfig extends AbstractConfig {
} }
public final boolean isSecureRequired(String uri) { public final boolean isSecureRequired(String uri) {
String[] secured = (String[])get(m_secureRequired); String[] secured = (String[]) get(m_secureRequired);
if (secured != null) { if (secured != null) {
for (int i=0, n=secured.length; i<n; i++) { for (int i = 0, n = secured.length; i < n; i++) {
if (uri.startsWith(secured[i])) { if (uri.startsWith(secured[i])) {
return true; return true;
} }
@ -202,9 +239,9 @@ public final class WebConfig extends AbstractConfig {
} }
public final boolean isNonSecureSwitchRequired(String uri) { public final boolean isNonSecureSwitchRequired(String uri) {
String[] switchBack = (String[])get(m_secureSwitchBack); String[] switchBack = (String[]) get(m_secureSwitchBack);
if (switchBack != null) { if (switchBack != null) {
for (int i=0, n=switchBack.length; i<n; i++) { for (int i = 0, n = switchBack.length; i < n; i++) {
if (uri.startsWith(switchBack[i])) { if (uri.startsWith(switchBack[i])) {
return true; return true;
} }
@ -217,9 +254,9 @@ public final class WebConfig extends AbstractConfig {
* Provide the name and port of the machine on which the CCM instance is * Provide the name and port of the machine on which the CCM instance is
* running. (Value of parameter waf.web.host) * running. (Value of parameter waf.web.host)
* *
* Used to fetch some resources by a local URL avoiding external * Used to fetch some resources by a local URL avoiding external internet
* internet traffic (and delay). If not specified set to the servers's * traffic (and delay). If not specified set to the servers's name
* name redirecting all traffic to external internet address. * redirecting all traffic to external internet address.
* *
* @return HttpHost object, contains internal name & port of the machine * @return HttpHost object, contains internal name & port of the machine
* hosting a CCM instance * hosting a CCM instance
@ -238,15 +275,14 @@ public final class WebConfig extends AbstractConfig {
/** /**
* *
* @return * @return @deprecated use Web.getContextPath() instead. The installation
* @deprecated use Web.getContextPath() instead. The installation context * context must no longer manually configured
* must no longer manually configured
*/ */
// NO LONGER configured by configuration option but determined at runtime // NO LONGER configured by configuration option but determined at runtime
// by CCMDispatcherServlet itself. // by CCMDispatcherServlet itself.
// // dispatcherContextPath option in old Initializer, set to "" // // dispatcherContextPath option in old Initializer, set to ""
// m_context = new StringParameter // m_context = new StringParameter
// ("waf.web.dispatcher_context_path", Parameter.REQUIRED, ""); // ("waf.web.dispatcher_context_path", Parameter.REQUIRED, "");
public final String getDispatcherContextPath() { public final String getDispatcherContextPath() {
// return (String) get(m_context); // return (String) get(m_context);
return CCMDispatcherServlet.getContextPath(); return CCMDispatcherServlet.getContextPath();
@ -261,12 +297,13 @@ public final class WebConfig extends AbstractConfig {
} }
/** /**
* Gets the system default cache policy. This value is set via * Gets the system default cache policy. This value is set via the
* the <code>com.arsdigita.web.cache_policy</code> system property * <code>com.arsdigita.web.cache_policy</code> system property using one fo
* using one fo the following values: <code>user</code> for * the following values: <code>user</code> for per-user caching,
* per-user caching, <code>world</code> for globally enabled * <code>world</code> for globally enabled caching, <code>disable</code> to
* caching, <code>disable</code> to prevent HTTP header caching, and * prevent HTTP header caching, and <code>none</code>to always prevent
* <code>none</code>to always prevent caching in any case. * caching in any case.
*
* @return * @return
*/ */
public final CachePolicy getCachePolicy() { public final CachePolicy getCachePolicy() {
@ -274,7 +311,8 @@ public final class WebConfig extends AbstractConfig {
} }
private static class DispatcherServletPathParameter private static class DispatcherServletPathParameter
extends StringParameter { extends StringParameter {
DispatcherServletPathParameter(final String name) { DispatcherServletPathParameter(final String name) {
super(name); super(name);
} }
@ -284,14 +322,16 @@ public final class WebConfig extends AbstractConfig {
final String string = (String) value; final String string = (String) value;
if (string.endsWith("/")) { if (string.endsWith("/")) {
final ParameterError error = new ParameterError final ParameterError error = new ParameterError(this,
(this, "The value must not end in a '/'"); "The value must not end in a '/'");
errors.add(error); errors.add(error);
} }
} }
} }
private static class DefaultSchemeParameter extends EnumerationParameter { private static class DefaultSchemeParameter extends EnumerationParameter {
DefaultSchemeParameter(final String name, DefaultSchemeParameter(final String name,
final int multiplicity, final int multiplicity,
final Object defaalt) { final Object defaalt) {
@ -300,9 +340,11 @@ public final class WebConfig extends AbstractConfig {
put("http", "http"); put("http", "http");
put("https", "https"); put("https", "https");
} }
} }
private static class CachePolicyParameter extends EnumerationParameter { private static class CachePolicyParameter extends EnumerationParameter {
CachePolicyParameter(final String name, CachePolicyParameter(final String name,
final int multiplicity, final int multiplicity,
final Object defaalt) { final Object defaalt) {
@ -313,6 +355,7 @@ public final class WebConfig extends AbstractConfig {
put("user", CachePolicy.USER); put("user", CachePolicy.USER);
put("world", CachePolicy.WORLD); put("world", CachePolicy.WORLD);
} }
} }
protected DynamicHostProvider dhProvider = null; protected DynamicHostProvider dhProvider = null;
@ -326,7 +369,9 @@ public final class WebConfig extends AbstractConfig {
Class klass = Class.forName(classname); Class klass = Class.forName(classname);
dhProvider = (DynamicHostProvider) klass.newInstance(); dhProvider = (DynamicHostProvider) klass.newInstance();
} catch (Exception e) { } catch (Exception e) {
s_log.error("Could not instantiate DynamicHostProvider using classname : "+classname, e); s_log.error(
"Could not instantiate DynamicHostProvider using classname : "
+ classname, e);
} }
} }
dhProviderInited = true; dhProviderInited = true;
@ -335,32 +380,29 @@ public final class WebConfig extends AbstractConfig {
} }
public final boolean getDeactivateCacheHostNotifications() { public final boolean getDeactivateCacheHostNotifications() {
return ((Boolean) get(m_deactivate_cache_host_notifications)).booleanValue(); return ((Boolean) get(m_deactivate_cache_host_notifications))
.booleanValue();
} }
// //
// Deprecated classes and methods // Deprecated classes and methods
// //
/** /**
* @return * @return @deprecated Use <code>getServer().getName()</code> instead.
* @deprecated Use <code>getServer().getName()</code> instead.
*/ */
public final String getServerName() { public final String getServerName() {
return getServer().getName(); return getServer().getName();
} }
/** /**
* @return * @return @deprecated Use <code>getServer().getPort()</code> instead.
* @deprecated Use <code>getServer().getPort()</code> instead.
*/ */
public final int getServerPort() { public final int getServerPort() {
return getServer().getPort(); return getServer().getPort();
} }
/** /**
* @return * @return @deprecated Use
* @deprecated Use
* <code>Host.retrieve(Web.getConfig().getHost())</code> instead. * <code>Host.retrieve(Web.getConfig().getHost())</code> instead.
*/ */
public final Host getCurrentHost() { public final Host getCurrentHost() {
@ -368,8 +410,7 @@ public final class WebConfig extends AbstractConfig {
} }
/** /**
* @return * @return @deprecated Use <code>Host.retrieveAll()</code> instead.
* @deprecated Use <code>Host.retrieveAll()</code> instead.
*/ */
public final Host[] getHosts() { public final Host[] getHosts() {
final List hosts = new ArrayList(); final List hosts = new ArrayList();
@ -383,4 +424,13 @@ public final class WebConfig extends AbstractConfig {
return (Host[]) hosts.toArray(new Host[hosts.size()]); return (Host[]) hosts.toArray(new Host[hosts.size()]);
} }
public final String getVaryHeaders() {
return (String) get(m_varyHeaders);
}
public void setVaryHeaders(final String value) {
set(m_varyHeaders, value);
}
} }

View File

@ -57,3 +57,8 @@ waf.web.dynamic_host_provider.title=Dynamic Host Provider
waf.web.dynamic_host_provider.purpose=Class name of the DynamicHostProvider to use waf.web.dynamic_host_provider.purpose=Class name of the DynamicHostProvider to use
waf.web.dynamic_host_provider.example=com.arsdigita.web.ServerDynamicHostProvider waf.web.dynamic_host_provider.example=com.arsdigita.web.ServerDynamicHostProvider
waf.web.dynamic_host_provider.format=[string] waf.web.dynamic_host_provider.format=[string]
waf.web.vary_headers.title=Value of vary header
waf.web.vary_headers.purpose=Value of the vary header in the HTTP response. Set the an empty string to disable.
waf.web.vary_headers.example=accept-language
waf.web.vary_headers.format=[string]

View File

@ -1,5 +1,8 @@
package com.arsdigita.bundle; package com.arsdigita.bundle;
import com.arsdigita.globalization.GlobalizationHelper;
import com.arsdigita.web.WebConfig;
import java.io.IOException; import java.io.IOException;
import javax.servlet.Filter; import javax.servlet.Filter;
@ -8,7 +11,10 @@ import javax.servlet.FilterConfig;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/** /**
* *
@ -31,7 +37,24 @@ public class AddVaryHeaderFilter implements Filter {
final HttpServletResponse response final HttpServletResponse response
= (HttpServletResponse) servletResponse; = (HttpServletResponse) servletResponse;
response.addHeader("Vary", "accept-language"); final WebConfig webConfig = WebConfig.getInstanceOf();
if (webConfig.getVaryHeaders() != null
&& !webConfig.getVaryHeaders().isEmpty()) {
response
.addHeader("Vary", webConfig.getVaryHeaders());
}
final HttpSession session = ((HttpServletRequest) servletRequest)
.getSession();
if (session != null && session.getAttribute(
GlobalizationHelper.LANG_PARAM) != null) {
response.addCookie(new Cookie(
GlobalizationHelper.LANG_PARAM,
(String) session.getAttribute(
GlobalizationHelper.LANG_PARAM)));
}
// response.addHeader("Vary", "accept-language");
filterChain.doFilter(servletRequest, servletResponse); filterChain.doFilter(servletRequest, servletResponse);
} }