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.util.Traversal;
import com.arsdigita.developersupport.DeveloperSupport;
import com.arsdigita.globalization.GlobalizationHelper;
import com.arsdigita.kernel.Kernel;
import com.arsdigita.profiler.Profiler;
import com.arsdigita.util.Assert;
import com.arsdigita.util.SystemInformation;
import com.arsdigita.web.WebConfig;
import com.arsdigita.xml.Document;
import com.arsdigita.xml.Element;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
@ -45,21 +48,27 @@ import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.ParserConfigurationException;
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.
*
* <UL>
* <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
* this page has been received through {@link ActionListener ActionListeners}.</LI>
* <LI>Tracks request parameters for stateful components, such as tabbed panes and sortable
* tables.</LI>
* <LI>Provides methods for servicing requests and for notifying other
* components that a request for this page has been received through
* {@link ActionListener ActionListeners}.</LI>
* <LI>Tracks request parameters for stateful components, such as tabbed panes
* and sortable tables.</LI>
* </UL>
*
* 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 = ".";
/**
* The prefix that gets prepended to all state variables. Components must not use variables
* starting with this prefix. This guarantees that the page state and variables individual
* components wish to pass do not interfere with each other.
* The prefix that gets prepended to all state variables. Components must
* not use variables starting with this prefix. This guarantees that the
* 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 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 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
* m_invisible.
* The name of the request parameter used for the visibility state of
* components stored in m_invisible.
*/
static final String INVISIBLE = INTERNAL + "i";
/**
* Map of stateful components (id --> Component) SortedMap used because component based hash for
* page is based on concatenation of component ids, and so need to guarantee that they are
* returned in the same order for the same page - cg.
* Map of stateful components (id --> Component) SortedMap used because
* component based hash for page is based on concatenation of component ids,
* and so need to guarantee that they are returned in the same order for the
* same page - cg.
*/
private SortedMap m_componentMap;
private List m_components;
@ -132,32 +144,35 @@ public class Page extends SimpleComponent implements Container {
private List m_actionListeners;
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
* to allow developers to add PrintListeners to dynamically change the value of the title.
* The title of the page to be added in the head of HTML output. 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;
/**
* Stores the actual title for the current request. The title may be generated with a
* PrintListener of the m_title Label.
* Stores the actual title for the current request. The title may be
* generated with a PrintListener of the m_title Label.
*/
private RequestLocal m_currentTitle;
/**
* A list of all the client-side stylesheets. The elements of the list are of type
* Page.Stylesheet, defined at the end of this file.
* A list of all the client-side stylesheets. The elements of the list are
* of type Page.Stylesheet, defined at the end of this file.
*/
private List m_clientStylesheets;
private StringParameter m_selected;
private StringParameter m_controlEvent;
private StringParameter m_controlValue;
/**
* The default (initial) visibility of components. The encoding is identical to that for
* PageState.m_invisible.
* The default (initial) visibility of components. The encoding is identical
* 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;
/**
* 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;
/**
@ -165,39 +180,41 @@ public class Page extends SimpleComponent implements Container {
*/
private boolean m_finished = false;
/**
* indicates whether pageState.stateAsURL() should export the entire state for this page, or
* whether it should only export the control event as a URL and use the HttpSession for the rest
* of the page state.
* indicates whether pageState.stateAsURL() should export the entire state
* for this page, or whether it should only export the control event as a
* URL and use the HttpSession for the rest of the page state.
*/
private boolean m_useHttpSession = false;
/**
* Returns <code>true</code> if this page should export state through the HttpSession instead of
* the URL query string.
* Returns <code>true</code> if this page should export state through the
* HttpSession instead of the URL query string.
* <P>
* If this returns <code>true</code>, then PageState.stateAsURL() will only export the control
* event as a URL query string. If this returns <code>false</code>, then stateAsURL() will
* export the entire page state.
* If this returns <code>true</code>, then PageState.stateAsURL() will only
* export the control event as a URL query string. If this returns
* <code>false</code>, then stateAsURL() will export the entire page state.
*
* @see PageState#stateAsURL
*
* @return <code>true</code> if this page should export state through the HttpSession;
* <code>false</code> if it should export using the URL query string.
* @return <code>true</code> if this page should export state through the
* HttpSession; <code>false</code> if it should export using the URL
* query string.
*/
public boolean isUsingHttpSession() {
return m_useHttpSession;
}
/**
* Indicates to this page whether it should export its entire state to subsequent requests
* through the URL query string, or if it should use the HttpSession instead and only use the
* URL query string for the control event.
* Indicates to this page whether it should export its entire state to
* subsequent requests through the URL query string, or if it should use the
* HttpSession instead and only use the URL query string for the control
* event.
*
* @see PageState#stateAsURL
*
* @param b <code>true</code> if PageState.stateAsURL() will export only the control event as a
* URL query string. <code>false</code> if stateAsURL() will export the entire page
* state.
* @param b <code>true</code> if PageState.stateAsURL() will export only the
* control event as a URL query string. <code>false</code> if
* stateAsURL() will export the entire page state.
*/
public void setUsingHttpSession(boolean 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
*/
@ -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
*/
@ -304,12 +323,13 @@ public class Page extends SimpleComponent implements Container {
}
/**
* Adds a component with the specified layout constraints to this container. Layout constraints
* are defined in each layout container as static ints. To specify multiple constraints, use
* bitwise OR.
* Adds a component with the specified layout constraints to this container.
* Layout constraints are defined in each layout container as static ints.
* To specify multiple constraints, use bitwise OR.
*
* @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
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
* <code>true</code> if and only if this list contains at least one element e such that (o==null
* ? e==null : o.equals(e)).
* Returns <code>true</code> if this list contains the specified element.
* More formally, returns <code>true</code> if and only if this list
* contains at least one element e such that (o==null ? e==null :
* o.equals(e)).
* <P>
* This method returns <code>true</code> only if the component has been directly added to this
* container. If this container contains another container that contains this component, this
* method returns <code>false</code>.
* This method returns <code>true</code> only if the component has been
* directly added to this container. If this container contains another
* container that contains this component, this method returns
* <code>false</code>.
*
* @param o element whose presence in this container is to be tested
*
* @return <code>true</code> if this Container contains the specified component directly;
* <code>false</code> otherwise.
* @return <code>true</code> if this Container contains the specified
* component directly; <code>false</code> otherwise.
*/
@Override
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
* index. Since the user has no control over the index of added components (other than counting
* each call to add), this method should be used in conjunction with indexOf.
* Returns the component at the specified position. Each call to the add
* method increments the index. Since the user has no control over the index
* 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
*
@ -354,12 +377,11 @@ public class Page extends SimpleComponent implements Container {
*
* @param c component to search for
*
* @return the index in this list of the first occurrence of the specified element, or -1 if
* this list does not contain this element.
* @return the index in this list of the first occurrence of the specified
* element, or -1 if this list does not contain this element.
*
* @pre c != null
* @post contains(c) implies (return >= 0) && (return < size())
* @pos
* @post contains(c) implies (return >= 0) && (return < size()) @pos
* t !contains(c) implies return == -1
*/
@Override
@ -370,8 +392,8 @@ public class Page extends SimpleComponent implements Container {
/**
* Returns <code>true</code> if the container contains no components.
*
* @return <code>true</code> if this container contains no components; <code>false</code>
* otherwise.
* @return <code>true</code> if this container contains no components;
* <code>false</code> otherwise.
*/
@Override
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
* components that are indirectly contained in this container.
* Returns the number of elements in this container. This does not
* recursively count the components that are indirectly contained 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.
*/
@ -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
* this function, as the existing container is simply overwritten.
* Set the Container used for rendering components on this page. Caution
* should be used with this function, as the existing container is simply
* overwritten.
*
* @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
* {@link PageState}. Any validation error in the <code>PageState</code> will cause the
* <code>Page</code> to completely ignore all other components and render only the error display
* component.
* Sets the {@link Component} that will display the validation errors in the
* current {@link PageState}. Any validation error in the
* <code>PageState</code> will cause the <code>Page</code> to completely
* ignore all other components and render only the error display component.
* <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
* <code>PageState</code>
* @param c the component that will display the validation errors in the
* current <code>PageState</code>
*/
public final void setErrorDisplay(Component c) {
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
* {@link PageState}. Any validation error in the <code>PageState</code> will cause the
* <code>Page</code> to completely ignore all other components and render only the error display
* component.
* Gets the {@link Component} that will display the validation errors in the
* current {@link PageState}. Any validation error in the
* <code>PageState</code> will cause the <code>Page</code> to completely
* ignore all other components and render only the error display component.
* <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
* <code>PageState</code>.
* @return the component that will display the validation errors in the
* current <code>PageState</code>.
*/
public final Component getErrorDisplay() {
return m_errorDisplay;
}
/**
* Adds a client-side stylesheet that should be used in HTML output. Arbitrarily many
* client-side stylesheets can be added with this method. To use a CSS stylesheet, call
* something like <code>setStyleSheet("style.css", "text/css")</code>.
* Adds a client-side stylesheet that should be used in HTML output.
* Arbitrarily many client-side stylesheets can be added with this method.
* To use a CSS stylesheet, call something like
* <code>setStyleSheet("style.css", "text/css")</code>.
*
* <p>
* These values will ultimately wind up in a <tt>&lt;link&gt;</tt>
* tag in the head of the HTML page.
*
* <p>
* Note that the stylesheet set with this call has nothing to do with the XSLT stylesheet
* (transformer) that is applied to the XML generated from this page!
* Note that the stylesheet set with this call has nothing to do with the
* XSLT stylesheet (transformer) that is applied to the XML generated from
* this page!
*
* @param styleSheetURI the location of the stylesheet
* @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
* preserved between requests, but that have no special connection to any of the components on
* the page. For a page that displays details about an item, a global parameter would be used to
* identify the item.
* Adds a global state parameter to this page. Global parameters are values
* that need to be preserved between requests, but that have no special
* connection to any of the components on the page. For a page that displays
* 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
* and stays unmangled.
* If the parameter was previously added as a component state parameter, its
* name is unmangled and stays unmangled.
*
* @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)
* below.
* Constructs the top nodes of the DOM or JDOM tree. Used by
* generateXML(PageState, Document) below.
* <p>
* Generates DOM fragment:
* <pre>
@ -543,7 +573,8 @@ public class Page extends SimpleComponent implements Container {
* ... page content gnerated by children ...
* &lt;/bebop:page></pre> The content of the <tt>&lt;title&gt;</tt>
* 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}.
*
* @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
* that results from the {@link javax.servlet.http.HttpServletRequest} kept in the
* Constructs a DOM or JDOM tree with all components on the page. The tree
* represents the page that results from the
* {@link javax.servlet.http.HttpServletRequest} kept in the
* <code>state</code>.
*
* @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.
getRequest())) {
Element structure = page.newChildElement("bebop:structure", BEBOP_XML_NS);
Element structure = page.newChildElement("bebop:structure",
BEBOP_XML_NS);
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) {
@ -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
* component. Processes a request by notifying the component from which the process originated
* and {@link #fireActionEvent
* broadcasts} an {@link ActionEvent} to all the listeners that registered with
* {@link #addActionListener addActionListener}.
* Creates a PageState object and processes it by calling the respond method
* on the selected component. Processes a request by notifying the component
* from which the process originated and {@link #fireActionEvent
* broadcasts} an {@link ActionEvent} to all the listeners that registered
* with {@link #addActionListener addActionListener}.
*
* @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
* on the selected Bebop component.
* Processes the supplied PageState object according to this PageModel.
* Calls the respond method on the selected Bebop component.
*/
public void process(PageState state) throws ServletException {
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
* current set of components in this Page, calling generateXML on each. Does NOT do the
* rendering. If the HTTP response has already been committed, does not build the XML document.
* Builds a DOM Document from the current request state by doing a
* depth-first tree walk on the current set of components in this Page,
* 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)
*/
@ -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
* add its state parameters to the page's state model.
* Finishes building the page. The tree of components is traversed and each
* component is told to add its state parameters to the page's state model.
*
* @pre ! isLocked()
*/
@ -760,7 +817,8 @@ public class Page extends SimpleComponent implements Container {
* Locks the page and all its components against further modifications.
*
* <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
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
* selected component has had a chance to respond.
* Registers a listener that is notified whenever a request to this page is
* made, after the selected component has had a chance to respond.
*
* @pre l != null
* @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
* selected component has had a chance to respond.
* Registers a listener that is notified whenever a request to this page is
* made, before the selected component has had a chance to respond.
*
* @pre l != null
* @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
* this page, and the state recorded in the event is the one resulting from processing the
* current request.
* Broadcasts an {@link ActionEvent} to all registered listeners. The source
* of the event is this page, and the state recorded in the event is the one
* resulting from processing the current request.
*
* @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
* this page, and the state recorded in the event is the one resulting from processing the
* current request.
* Broadcasts a {@link RequestEvent} to all registered listeners. The source
* of the event is this page, and the state recorded in the event is the one
* resulting from processing the current request.
*
* @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
* information as key value. In general this should include generator name and generator
* version.
* Export page generator information if set. The m_pageGenerator is a
* HashMap containing the information as key value. In general this should
* include generator name and generator version.
*
* @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) {
SystemInformation sysInfo = SystemInformation.getInstance();
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();
while (keyValues.hasNext()) {
@ -944,7 +1003,8 @@ public class Page extends SimpleComponent implements Container {
if (!stateContains(c)) {
if (c == null) {
s_log.error("c is null");
} /*else {
}
/*else {
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
* parameter several times, from the same or different components. The name of the parameter
* will be changed to ensure 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.
* Registers a state parameter for a component. It is permissible to
* register the same state parameter several times, from the same or
* different components. The name of the parameter will be changed to ensure
* 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 p the state parameter to register
@ -986,8 +1048,9 @@ public class Page extends SimpleComponent implements Container {
}
if (!m_stateModel.containsFormParam(p)) {
String name = parameterName(c, p.getName());
s_log.debug(String.format("Setting name of parameter to add to '%s'",
name));
s_log.debug(String
.format("Setting name of parameter to add to '%s'",
name));
p.setName(name);
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
* register traveral
* Gets the state index of a component. This is the number assigned to the
* component in the register traveral
*
* @param c the component to search for
*
* @return the index in this list of the first occurrence of the specified element, or -1 if
* this list does not contain this element.
* @return the index in this list of the first occurrence of the specified
* element, or -1 if this list does not contain this element.
*
* @pre c != null
* @post contains(c) implies (return >= 0) && (return < size()) @pos
* t !contains(c) implies return == -1
* @post contains(c) implies (return >= 0) && (return < size()) @pos t
* !contains(c) implies return == -1
*/
public int stateIndex(Component c) {
return m_components.indexOf(c);
@ -1046,8 +1109,7 @@ public class Page extends SimpleComponent implements Container {
/**
* Gets a page component by index.
*
* @pre (i >= 0) && (i < size()) @pos
* t return != null
* @pre (i >= 0) && (i < size()) @pos t return != null
*/
public Component getComponent(int 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
*
* @return <code>true</code> if the component is visible by default; <code>false</code>
* otherwise.
* @return <code>true</code> if the component is visible by default;
* <code>false</code> otherwise.
*
* @see #setVisibleDefault setVisibleDefault
* @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
* when a page is displayed for the first time and on subsequent requests until the visibility
* of a component is changed explicitly with {@link Component#setVisible
* Sets whether the specified component is visible by default. The default
* visibility is used when a page is displayed for the first time and on
* subsequent requests until the visibility of a component is changed
* explicitly with {@link Component#setVisible
* Component.setVisible}.
*
* <p>
* When a component is first added to a page, it is visible.
*
* @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#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) {
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) {
if (c == null) {
@ -1219,10 +1285,12 @@ public class Page extends SimpleComponent implements Container {
selected.setText(sel);
// 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.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.setText(req.getParameter(m_controlValue.getName()));
@ -1231,7 +1299,8 @@ public class Page extends SimpleComponent implements Container {
for (Iterator ii = getStateModel().getParameters(); ii.hasNext();) {
ParameterModel p = (ParameterModel) ii.next();
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.setText(String.valueOf(s.getValue(p)));
}
@ -1260,11 +1329,13 @@ public class Page extends SimpleComponent implements Container {
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("defaultValue",
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();) {
@ -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
* situations where only the components present is of importance, this may be used by
* implementations of hashCode & equals
* return a string that represents an ordered list of component ids used on
* the page. For situations where only the components present is of
* importance, this may be used by implementations of hashCode & equals
*
* @return
*/
@ -1304,7 +1375,8 @@ public class Page extends SimpleComponent implements Container {
String componentId = (String) it.next();
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()));
return hashString.toString();

View File

@ -21,7 +21,7 @@ import javax.servlet.http.HttpSession;
public class GlobalizationHelper {
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
private GlobalizationHelper() {

View File

@ -23,6 +23,7 @@ import com.arsdigita.developersupport.DeveloperSupport;
import com.arsdigita.dispatcher.DispatcherHelper;
import com.arsdigita.dispatcher.RequestEvent;
import com.arsdigita.domain.DataObjectNotFoundException;
import com.arsdigita.globalization.GlobalizationHelper;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.persistence.TransactionContext;
import com.arsdigita.profiler.Profiler;
@ -40,23 +41,26 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
// NOTE
// Combines and replaces the previous classes DispatcherServlet and BaseDispatcher
// Most of their code and their separation are abundant as old style applications
// are no longer supported.
/**
* <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>
* 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
* application and forwards to the servlet associated with that application.
* If instead no application is found, a 404 response is generated.</p>
* application and forwards to the servlet associated with that application. If
* 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>
* &lt;servlet&gt;
@ -70,7 +74,8 @@ import org.apache.log4j.Logger;
* &lt;/servlet-mapping&gt;
* </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>
*
* <blockquote><pre>
@ -84,101 +89,116 @@ import org.apache.log4j.Logger;
* </pre></blockquote>
*
* @see com.arsdigita.web.BaseApplicationServlet
* @author Justin Ross &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;
* @author Justin Ross
* &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 $
*/
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
* and set com.arsdigita.web.CCMDispatcherServlet=DEBUG by uncommenting
* or adding the line. */
/**
* Internal logger instance to faciliate debugging. Enable logging output by
* editing /WEB-INF/conf/log4j.properties int hte runtime environment and
* set com.arsdigita.web.CCMDispatcherServlet=DEBUG by uncommenting or
* adding the line.
*/
private static final Logger s_log = Logger.getLogger(
CCMDispatcherServlet.class);
CCMDispatcherServlet.class);
static final String DISPATCHED_ATTRIBUTE =
CCMDispatcherServlet.class.getName() + ".dispatched";
static final String DISPATCHED_ATTRIBUTE = CCMDispatcherServlet.class
.getName() + ".dispatched";
/** Instance of the private Cache class */
/**
* Instance of the private Cache class
*/
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
* directory containing the web.xml configuring this CCMDispatcherServlet
* is located in the servlet container webapps directory.
* */
/**
* String containing the web context path portion of the WEB application
* where this CCMDispatcherServlet is executed. (I.e. where the WEB-INF
* directory containing the web.xml configuring this CCMDispatcherServlet is
* located in the servlet container webapps directory.
*
*/
private static String s_contextPath;
public final boolean isApplicationInCache(String path) {
return s_cache.isCached(path);
}
/**
* Servlet initializer uses the extension point of parent class.
* @throws ServletException
* Servlet initializer uses the extension point of parent class.
*
* @throws ServletException
*/
@Override
public void doInit() throws ServletException {
ServletContext servletContext = getServletContext();
s_contextPath = servletContext.getContextPath();
// For backwords compatibility reasons register the web application
// context of the Core (root) application als "/"
// Web.registerServletContext("/",
// servletContext);
// Web.registerServletContext("/",
// servletContext);
}
/**
* Extends the standard service() method of the parent BaseServlet class.
* Looks up and identifies the web application addressed in the url and
* forwards to that application's ApplicationServlet.
* (new style legacy free)
*
* @param sreq
* @param sresp
* forwards to that application's ApplicationServlet. (new style legacy
* free)
*
* @param servletRequest
* @param servletResponse
*
* @throws ServletException
* @throws IOException
* @throws IOException
*/
@Override
protected void doService(final HttpServletRequest sreq,
final HttpServletResponse sresp)
throws ServletException, IOException {
DeveloperSupport.requestStart(new RequestEvent(sreq, sresp,
@Override
protected void doService(final HttpServletRequest servletRequest,
final HttpServletResponse servletResponse)
throws ServletException, IOException {
DeveloperSupport.requestStart(new RequestEvent(servletRequest,
servletResponse,
null, true, false));
if (s_log.isDebugEnabled()) {
s_log.debug("Dispatching request " + sreq.getRequestURI() + " [" +
sreq.getContextPath() + "," +
sreq.getServletPath() + "," +
sreq.getPathInfo() + "," +
sreq.getQueryString() + "]");
s_log.debug("Dispatching request " + servletRequest.getRequestURI()
+ " ["
+ servletRequest.getContextPath() + ","
+ servletRequest
.getServletPath()
+ "," + servletRequest.getPathInfo() + ","
+ servletRequest
.getQueryString()
+ "]");
}
DeveloperSupport.startStage("CCMDispatcherServlet.doService");
final String path = sreq.getPathInfo();
final String path = servletRequest.getPathInfo();
if (requiresTrailingSlash(path)) {
s_log.debug("The request URI needs a trailing slash; " +
"redirecting");
s_log.debug("The request URI needs a trailing slash; "
+ "redirecting");
final String prefix = DispatcherHelper.getDispatcherPrefix(sreq);
String uri = sreq.getRequestURI();
final String prefix = DispatcherHelper.getDispatcherPrefix(
servletRequest);
String uri = servletRequest.getRequestURI();
if (prefix != null && prefix.trim().length() > 0) {
uri = prefix + uri;
}
final String query = sreq.getQueryString();
final String query = servletRequest.getQueryString();
String url = null;
if (query == null) {
sresp.sendRedirect(sresp.encodeRedirectURL(uri + "/"));
servletResponse.sendRedirect(servletResponse.encodeRedirectURL(
uri + "/"));
} else {
sresp.sendRedirect
(sresp.encodeRedirectURL(uri + "/?" + query));
servletResponse.sendRedirect(servletResponse.encodeRedirectURL(
uri + "/?" + query));
}
// return true;
@ -186,41 +206,64 @@ public class CCMDispatcherServlet extends BaseServlet {
Assert.exists(path, String.class);
s_log.debug("Storing the path elements of the current request as " +
"the original path elements");
s_log.debug("Storing the path elements of the current request as "
+ "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()) {
s_log.debug("Using path '" + path + "' to lookup application");
}
DeveloperSupport.startStage("CCMDispatcherServlet.lookupApplicationSpec");
DeveloperSupport.startStage(
"CCMDispatcherServlet.lookupApplicationSpec");
final ApplicationSpec spec = lookupApplicationSpec(path);
DeveloperSupport.endStage("CCMDispatcherServlet.lookupApplicationSpec");
DeveloperSupport.endStage(
"CCMDispatcherServlet.lookupApplicationSpec");
if (spec == null) {
s_log.debug("No application was found; doing nothing");
// return false;
// we have to create a 404 page here!
String requestUri = sreq.getRequestURI(); // same as ctx.getRemainingURLPart()
sresp.sendError(404, requestUri + " not found on this server.");
String requestUri = servletRequest.getRequestURI(); // same as ctx.getRemainingURLPart()
servletResponse.sendError(404, requestUri
+ " not found on this server.");
} else {
if (s_log.isDebugEnabled()) {
s_log.debug("Found application " + spec.getAppID() + "; " +
"dispatching to its servlet");
s_log.debug("Found application " + spec.getAppID() + "; "
+ "dispatching to its servlet");
}
sreq.setAttribute
(BaseApplicationServlet.APPLICATION_ID_ATTRIBUTE,
spec.getAppID());
sreq.setAttribute(DISPATCHED_ATTRIBUTE, Boolean.TRUE);
servletRequest.setAttribute(
BaseApplicationServlet.APPLICATION_ID_ATTRIBUTE,
spec.getAppID());
servletRequest.setAttribute(DISPATCHED_ATTRIBUTE, Boolean.TRUE);
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()
// return true;
}
}
@ -229,11 +272,11 @@ public class CCMDispatcherServlet extends BaseServlet {
}
/**
*
*
* @param path
* @return
*
* @return
*/
private boolean requiresTrailingSlash(final String path) {
if (s_log.isDebugEnabled()) {
@ -241,8 +284,8 @@ public class CCMDispatcherServlet extends BaseServlet {
}
if (path == null) {
s_log.debug("The path is null; the request needs a trailing " +
"slash");
s_log.debug("The path is null; the request needs a trailing "
+ "slash");
return true;
}
@ -252,32 +295,33 @@ public class CCMDispatcherServlet extends BaseServlet {
}
if (path.lastIndexOf(".") < path.lastIndexOf("/")) {
s_log.debug("The last fragment of the path has no '.', so we " +
"assume a directory was requested; a trailing " +
"slash is required");
s_log.debug("The last fragment of the path has no '.', so we "
+ "assume a directory was requested; a trailing "
+ "slash is required");
return true;
} else {
s_log.debug("The last fragment of the path appears to be a file " +
"name; no trailing slash is needed");
s_log.debug("The last fragment of the path appears to be a file "
+ "name; no trailing slash is needed");
return false;
}
}
/**
*
*
* @param contextPath
* @param target
* @param sreq
* @param sresp
*
* @throws ServletException
* @throws IOException
* @throws IOException
*/
private void forward(String contextPath,
final String target,
final HttpServletRequest sreq,
final HttpServletResponse sresp)
throws ServletException, IOException {
throws ServletException, IOException {
if (s_log.isDebugEnabled()) {
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
// final ServletContext context = Web.getServletContext(contextPath);
final ServletContext context = Web.getServletContext()
.getContext(contextPath);
.getContext(contextPath);
if (s_log.isDebugEnabled()) {
s_log.debug("From context " + Web.getServletContext() +
" to context " + context);
s_log.debug("From context " + Web.getServletContext()
+ " to context " + context);
}
final RequestDispatcher rd = context.getRequestDispatcher(target);
@ -306,19 +350,20 @@ public class CCMDispatcherServlet extends BaseServlet {
}
/**
*
*
* @param rd
* @param sreq
* @param sresp
*
* @throws ServletException
* @throws IOException
* @throws IOException
*/
final void forward(final RequestDispatcher rd,
HttpServletRequest sreq,
final HttpServletResponse sresp)
throws ServletException, IOException {
s_log.debug("Checking if this request needs to be forwarded or " +
"included " + sreq);
throws ServletException, IOException {
s_log.debug("Checking if this request needs to be forwarded or "
+ "included " + sreq);
// Just in case sreq is wrapped in one of our own classes (spec.
// MultipartHttpServletRequest), return unwrapped request, otherwise
@ -326,78 +371,98 @@ public class CCMDispatcherServlet extends BaseServlet {
sreq = DispatcherHelper.restoreOriginalRequest(sreq);
if (sreq.getAttribute("javax.servlet.include.request_uri") == null) {
s_log.debug("The attribute javax.servlet.include.request_uri " +
"is not set; forwarding " + sreq);
s_log.debug("The attribute javax.servlet.include.request_uri "
+ "is not set; forwarding " + sreq);
rd.forward(sreq, sresp);
} else {
s_log.debug("The attribute javax.servlet.include.request_uri " +
"is set; including " + sreq);
s_log.debug("The attribute javax.servlet.include.request_uri "
+ "is set; including " + sreq);
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
* @return
*
* @return
*/
private ApplicationSpec lookupApplicationSpec(final String path) {
if (s_log.isDebugEnabled()) {
s_log.debug("*** Starting application lookup for path '" +
path + "' ***");
s_log.debug("*** Starting application lookup for path '" + path
+ "' ***");
}
ApplicationSpec spec = s_cache.getAppSpec(path);
if ( spec == null ) {
if (spec == null) {
s_log.debug("There's no application to be found");
}
return spec;
}
/**
*
*
*/
static void scheduleRefresh() {
s_cache.scheduleRefresh();
}
public static String getContextPath() {
return s_contextPath;
}
/**
*
*
*/
/* Nothing specifically to destroy here
@Override
protected void doDestroy() {
}
*/
*/
/**
* Private class.
*/
private static class ApplicationSpec {
private final BigDecimal m_id;
private final String m_instanceURI;
private final String m_typeURI;
private final String m_typeContextPath;
/**
*
* @param app
*
* @param app
*/
ApplicationSpec(Application app) {
if ( app == null ) { throw new NullPointerException("app"); }
if (app == null) {
throw new NullPointerException("app");
}
m_id = app.getID();
m_instanceURI = app.getPath();
m_typeURI = app.getServletPath();
m_id = app.getID();
m_instanceURI = app.getPath();
m_typeURI = app.getServletPath();
m_typeContextPath = app.getContextPath();
if (Assert.isEnabled()) {
@ -409,38 +474,42 @@ 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
* 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
* executing in no specific context but CCM's default.
*
* @return The context path of the application's url, "" in case of
* executing in the ROOT context.
*/
String getTypeContextPath() {
if (m_typeContextPath.equals("") ) {
String getTypeContextPath() {
if (m_typeContextPath.equals("")) {
// app is running in CCM's default context, determine the
// actual one
return Web.getWebappContextPath();
} else {
return m_typeContextPath;
return m_typeContextPath;
}
}
/**
*
*
* @param path
* @return
*
* @return
*/
String target(final String path) {
if (s_log.isDebugEnabled()) {
s_log.debug("Building the target path from the request path '" +
path + "' and the spec " + this);
s_log.debug("Building the target path from the request path '"
+ path + "' and the spec " + this);
}
final StringBuffer target = new StringBuffer(128);
@ -460,37 +529,45 @@ public class CCMDispatcherServlet extends BaseServlet {
}
/**
*
*
* @param obj
* @return
*
* @return
*/
@Override
public boolean equals(Object obj) {
if ( obj==null ) { return false; }
if (obj == null) {
return false;
}
ApplicationSpec other = (ApplicationSpec) obj;
return m_id.equals(other.m_id) &&
equal(m_instanceURI, other.m_instanceURI) &&
equal(m_typeURI, other.m_typeURI) &&
equal(m_typeContextPath, other.m_typeContextPath);
return m_id.equals(other.m_id) && equal(m_instanceURI,
other.m_instanceURI)
&& equal(m_typeURI, other.m_typeURI) && equal(
m_typeContextPath, other.m_typeContextPath);
}
/**
*
*
* @param s1
* @param s2
* @return
*
* @return
*/
private boolean equal(String s1, String s2) {
if (s1==s2) { return true; }
if (s1==null) { return equal(s2, s1); }
if (s1 == s2) {
return true;
}
if (s1 == null) {
return equal(s2, s1);
}
return s1.equals(s2);
}
/**
*
* @return
*
* @return
*/
@Override
public int hashCode() {
@ -498,8 +575,8 @@ public class CCMDispatcherServlet extends BaseServlet {
}
/**
*
* @return
*
* @return
*/
@Override
public String toString() {
@ -512,22 +589,22 @@ public class CCMDispatcherServlet extends BaseServlet {
sb.append("typeContextPath=").append(m_typeContextPath);
return sb.append("]").toString();
}
}
/**
* Private class Cache caches (path, AppSpec) mappings.
*/
private static class Cache extends PathMapCache {
/** */
private static final ThreadLocal s_handleHere = new ThreadLocal() {
@Override
protected Object initialValue() {
return Boolean.FALSE;
}
};
@Override
protected Object initialValue() {
return Boolean.FALSE;
}
};
/**
* Constructor, just delegates to Super class
@ -539,23 +616,28 @@ public class CCMDispatcherServlet extends BaseServlet {
// implements the PathMapCache interface
@Override
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 '/'.");
if (path == null) {
throw new NullPointerException("path");
}
return path.endsWith("/") ?
path : path.substring(0, path.lastIndexOf('/') + 1);
if (!path.startsWith("/")) {
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
@Override
public Object retrieve(String path) {
if ( "/".equals(path) ) { return null; }
if ("/".equals(path)) {
return null;
}
final TransactionContext context =
SessionManager.getSession().getTransactionContext();
if ( !context.inTxn() ) {
final TransactionContext context = SessionManager.getSession()
.getTransactionContext();
if (!context.inTxn()) {
s_log.debug("Beginning transaction");
context.beginTxn();
s_handleHere.set(Boolean.TRUE);
@ -563,7 +645,7 @@ public class CCMDispatcherServlet extends BaseServlet {
Application app = Application.retrieveApplicationForPath(path);
return app==null ? null : new ApplicationSpec(app);
return app == null ? null : new ApplicationSpec(app);
}
// implements the PathMapCache interface
@ -580,12 +662,14 @@ public class CCMDispatcherServlet extends BaseServlet {
try {
return (ApplicationSpec) super.get(path);
} finally {
if ( s_handleHere.get() == Boolean.TRUE ) {
if (s_handleHere.get() == Boolean.TRUE) {
s_handleHere.set(Boolean.FALSE);
SessionManager.getSession().getTransactionContext().
commitTxn();
}
}
}
}
}
}

View File

@ -39,8 +39,8 @@ import org.apache.log4j.Logger;
/**
* A record containing server-session scoped configuration properties.
*
* Accessors of this class may return null. Developers should take
* care to trap null return values in their code.
* Accessors of this class may return null. Developers should take care to trap
* null return values in their code.
*
* @see com.arsdigita.web.Web
* @author Justin Ross &lt;jross@redhat.com&gt;
@ -48,111 +48,146 @@ import org.apache.log4j.Logger;
*/
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
* and set com.arsdigita.web.WebConfig=DEBUG by uncommenting it */
/**
* Internal logger instance to faciliate debugging. Enable logging output by
* 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 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
* environment.
*
* @return The <code>CMSConfig</code> record; it cannot be null
*/
/**
* 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
* environment.
*
* @return The <code>CMSConfig</code> record; it cannot be null
*/
public static synchronized WebConfig getInstanceOf() {
if (s_config == null) {
s_config = new WebConfig();
s_config.load();
}
return s_config;
}
}
// /////////////////////////////////////////////////////////////////////////
// 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) { @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();
}
};
/**
* 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) {
/** 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
("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);
@Override
public final Object getDefaultValue() {
final HttpHost host = getServer();
if (host == null) {
return null;
} else {
return host.toString();
}
}
/** Dispatcher servlet path. It's the prefix to the main entry point for
* any application request (CCMDispatcherServlet). By default /ccm */
private final Parameter m_servlet = new StringParameter
("waf.web.dispatcher_servlet_path", Parameter.REQUIRED, "/ccm");
};
/**
* 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) {
/** 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);
@Override
public final Object getDefaultValue() {
return getServer();
}
private final Parameter m_dynamic_host_provider = new StringParameter
("waf.web.dynamic_host_provider",
Parameter.OPTIONAL, "");
};
/**
* Constructor, but do NOT instantiate this class directly, use
* getInstanceOf() instead. (Singelton pattern!)
*
*/
/**
* 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(
"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);
/**
* Dispatcher servlet path. It's the prefix to the main entry point for any
* 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() {
register(m_scheme);
@ -167,6 +202,7 @@ public final class WebConfig extends AbstractConfig {
register(m_default_cache_policy);
register(m_deactivate_cache_host_notifications);
register(m_dynamic_host_provider);
register(m_varyHeaders);
loadInfo();
}
@ -175,10 +211,11 @@ public final class WebConfig extends AbstractConfig {
return (String) get(m_scheme);
}
/**
/**
* 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)
* E.g. example.com:80
* by CCM for the site. (Value of parameter waf.web.server) E.g.
* example.com:80
*
* @return HttpHost object, contains public name & port of the server (site)
*/
public final HttpHost getServer() {
@ -188,11 +225,11 @@ public final class WebConfig extends AbstractConfig {
public final HttpHost getSecureServer() {
return (HttpHost) get(m_secureServer);
}
public final boolean isSecureRequired(String uri) {
String[] secured = (String[])get(m_secureRequired);
String[] secured = (String[]) get(m_secureRequired);
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])) {
return true;
}
@ -202,9 +239,9 @@ public final class WebConfig extends AbstractConfig {
}
public final boolean isNonSecureSwitchRequired(String uri) {
String[] switchBack = (String[])get(m_secureSwitchBack);
String[] switchBack = (String[]) get(m_secureSwitchBack);
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])) {
return true;
}
@ -213,14 +250,14 @@ public final class WebConfig extends AbstractConfig {
return false;
}
/**
* 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)
*
* 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.
*
*
* 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.
*
* @return HttpHost object, contains internal name & port of the machine
* hosting a CCM instance
*/
@ -237,16 +274,15 @@ public final class WebConfig extends AbstractConfig {
}
/**
*
* @return
* @deprecated use Web.getContextPath() instead. The installation context
* must no longer manually configured
*
* @return @deprecated use Web.getContextPath() instead. The installation
* context must no longer manually configured
*/
// NO LONGER configured by configuration option but determined at runtime
// by CCMDispatcherServlet itself.
// // dispatcherContextPath option in old Initializer, set to ""
// m_context = new StringParameter
// ("waf.web.dispatcher_context_path", Parameter.REQUIRED, "");
// NO LONGER configured by configuration option but determined at runtime
// by CCMDispatcherServlet itself.
// // dispatcherContextPath option in old Initializer, set to ""
// m_context = new StringParameter
// ("waf.web.dispatcher_context_path", Parameter.REQUIRED, "");
public final String getDispatcherContextPath() {
// return (String) get(m_context);
return CCMDispatcherServlet.getContextPath();
@ -261,20 +297,22 @@ public final class WebConfig extends AbstractConfig {
}
/**
* Gets the system default cache policy. This value is set via
* the <code>com.arsdigita.web.cache_policy</code> system property
* using one fo the following values: <code>user</code> for
* per-user caching, <code>world</code> for globally enabled
* caching, <code>disable</code> to prevent HTTP header caching, and
* <code>none</code>to always prevent caching in any case.
* @return
* Gets the system default cache policy. This value is set via the
* <code>com.arsdigita.web.cache_policy</code> system property using one fo
* the following values: <code>user</code> for per-user caching,
* <code>world</code> for globally enabled caching, <code>disable</code> to
* prevent HTTP header caching, and <code>none</code>to always prevent
* caching in any case.
*
* @return
*/
public final CachePolicy getCachePolicy() {
return (CachePolicy) get(m_default_cache_policy);
}
private static class DispatcherServletPathParameter
extends StringParameter {
extends StringParameter {
DispatcherServletPathParameter(final String name) {
super(name);
}
@ -284,14 +322,16 @@ public final class WebConfig extends AbstractConfig {
final String string = (String) value;
if (string.endsWith("/")) {
final ParameterError error = new ParameterError
(this, "The value must not end in a '/'");
final ParameterError error = new ParameterError(this,
"The value must not end in a '/'");
errors.add(error);
}
}
}
private static class DefaultSchemeParameter extends EnumerationParameter {
DefaultSchemeParameter(final String name,
final int multiplicity,
final Object defaalt) {
@ -300,9 +340,11 @@ public final class WebConfig extends AbstractConfig {
put("http", "http");
put("https", "https");
}
}
private static class CachePolicyParameter extends EnumerationParameter {
CachePolicyParameter(final String name,
final int multiplicity,
final Object defaalt) {
@ -313,6 +355,7 @@ public final class WebConfig extends AbstractConfig {
put("user", CachePolicy.USER);
put("world", CachePolicy.WORLD);
}
}
protected DynamicHostProvider dhProvider = null;
@ -326,7 +369,9 @@ public final class WebConfig extends AbstractConfig {
Class klass = Class.forName(classname);
dhProvider = (DynamicHostProvider) klass.newInstance();
} 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;
@ -335,32 +380,29 @@ public final class WebConfig extends AbstractConfig {
}
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
//
/**
* @return
* @deprecated Use <code>getServer().getName()</code> instead.
* @return @deprecated Use <code>getServer().getName()</code> instead.
*/
public final String getServerName() {
return getServer().getName();
}
/**
* @return
* @deprecated Use <code>getServer().getPort()</code> instead.
* @return @deprecated Use <code>getServer().getPort()</code> instead.
*/
public final int getServerPort() {
return getServer().getPort();
}
/**
* @return
* @deprecated Use
* @return @deprecated Use
* <code>Host.retrieve(Web.getConfig().getHost())</code> instead.
*/
public final Host getCurrentHost() {
@ -368,8 +410,7 @@ public final class WebConfig extends AbstractConfig {
}
/**
* @return
* @deprecated Use <code>Host.retrieveAll()</code> instead.
* @return @deprecated Use <code>Host.retrieveAll()</code> instead.
*/
public final Host[] getHosts() {
final List hosts = new ArrayList();
@ -383,4 +424,13 @@ public final class WebConfig extends AbstractConfig {
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.example=com.arsdigita.web.ServerDynamicHostProvider
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;
import com.arsdigita.globalization.GlobalizationHelper;
import com.arsdigita.web.WebConfig;
import java.io.IOException;
import javax.servlet.Filter;
@ -8,7 +11,10 @@ import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
*
@ -31,7 +37,24 @@ public class AddVaryHeaderFilter implements Filter {
final HttpServletResponse response
= (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);
}