You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@struts.apache.org by gv...@apache.org on 2006/04/06 04:46:37 UTC
svn commit: r391884 - in /struts/shale/trunk/core-library/src: conf/
java/org/apache/shale/component/ java/org/apache/shale/faces/
java/org/apache/shale/renderer/ java/org/apache/shale/view/faces/
Author: gvanmatre
Date: Wed Apr 5 19:46:34 2006
New Revision: 391884
URL: http://svn.apache.org/viewcvs?rev=391884&view=rev
Log:
Fix for bugs 37932, 39171 and 39156 reported by Mark Shifman and Paul Devine.
Added:
struts/shale/trunk/core-library/src/java/org/apache/shale/faces/ValidatorRenderKit.java (with props)
struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorCommandRenderer.java (with props)
struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorInputRenderer.java (with props)
Modified:
struts/shale/trunk/core-library/src/conf/validator-rules.xml
struts/shale/trunk/core-library/src/java/org/apache/shale/component/ValidatorScript.java
struts/shale/trunk/core-library/src/java/org/apache/shale/view/faces/ViewViewHandler.java
Modified: struts/shale/trunk/core-library/src/conf/validator-rules.xml
URL: http://svn.apache.org/viewcvs/struts/shale/trunk/core-library/src/conf/validator-rules.xml?rev=391884&r1=391883&r2=391884&view=diff
==============================================================================
--- struts/shale/trunk/core-library/src/conf/validator-rules.xml (original)
+++ struts/shale/trunk/core-library/src/conf/validator-rules.xml Wed Apr 5 19:46:34 2006
@@ -419,7 +419,7 @@
<validator name="float"
classname="org.apache.commons.validator.GenericValidator"
method="isDouble"
- methodParams="double"
+ methodParams="java.lang.String"
depends=""
msg="errors.float"
jsFunctionName="FloatValidations">
Modified: struts/shale/trunk/core-library/src/java/org/apache/shale/component/ValidatorScript.java
URL: http://svn.apache.org/viewcvs/struts/shale/trunk/core-library/src/java/org/apache/shale/component/ValidatorScript.java?rev=391884&r1=391883&r2=391884&view=diff
==============================================================================
--- struts/shale/trunk/core-library/src/java/org/apache/shale/component/ValidatorScript.java (original)
+++ struts/shale/trunk/core-library/src/java/org/apache/shale/component/ValidatorScript.java Wed Apr 5 19:46:34 2006
@@ -21,6 +21,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.StringTokenizer;
import javax.faces.component.EditableValueHolder;
@@ -31,6 +32,7 @@
import javax.faces.el.ValueBinding;
import org.apache.commons.validator.ValidatorAction;
+import org.apache.shale.renderer.ValidatorInputRenderer;
import org.apache.shale.validator.CommonsValidator;
/**
@@ -147,11 +149,11 @@
/**
* <p>Recursively finds all Commons validators for the all of the
- * components in a component hierarchy and adds them to a map.
+ * components in a component hierarchy and adds them to a map.</p>
* <p>If a validator's type is required, this method sets the
* associated component's required property to true. This is
* necessary because JSF does not validate empty fields unless
- * a component's required property is true.
+ * a component's required property is true.</p>
*
* @param c The component at the root of the component tree
* @param context The FacesContext for this request
@@ -164,16 +166,39 @@
if (vs[i] instanceof CommonsValidator) {
CommonsValidator v = (CommonsValidator) vs[i];
if (Boolean.TRUE.equals(v.getClient())) {
- String id = c.getClientId(context);
- addValidator(v.getType(), id, v);
-
- ValidatorAction action = v.getValidatorAction();
- List list = action.getDependencyList();
- Iterator iter = list.iterator();
- while (iter.hasNext()) {
- String type = (String) iter.next();
- addValidator(type, id, v);
- }
+
+ //look for the clientId set
+ Set clientIds = (Set) c.getAttributes().get(ValidatorInputRenderer.VALIDATOR_CLIENTIDS_ATTR);
+ if (clientIds != null) {
+ Iterator ci = clientIds.iterator();
+ while (ci.hasNext()) {
+ String id = (String) ci.next();
+ addValidator(v.getType(), id, v);
+
+ ValidatorAction action = v.getValidatorAction();
+ List list = action.getDependencyList();
+ Iterator iter = list.iterator();
+ while (iter.hasNext()) {
+ String type = (String) iter.next();
+ addValidator(type, id, v);
+ }
+
+ }
+
+ } else {
+ //otherwise just try using the client id
+ String id = c.getClientId(context);
+ addValidator(v.getType(), id, v);
+
+ ValidatorAction action = v.getValidatorAction();
+ List list = action.getDependencyList();
+ Iterator iter = list.iterator();
+ while (iter.hasNext()) {
+ String type = (String) iter.next();
+ addValidator(type, id, v);
+ }
+ }
+
}
if (Boolean.TRUE.equals(v.getServer())) {
// Fields with empty values are not validated, so
Added: struts/shale/trunk/core-library/src/java/org/apache/shale/faces/ValidatorRenderKit.java
URL: http://svn.apache.org/viewcvs/struts/shale/trunk/core-library/src/java/org/apache/shale/faces/ValidatorRenderKit.java?rev=391884&view=auto
==============================================================================
--- struts/shale/trunk/core-library/src/java/org/apache/shale/faces/ValidatorRenderKit.java (added)
+++ struts/shale/trunk/core-library/src/java/org/apache/shale/faces/ValidatorRenderKit.java Wed Apr 5 19:46:34 2006
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.shale.faces;
+
+import java.io.OutputStream;
+import java.io.Writer;
+
+import javax.faces.context.ResponseStream;
+import javax.faces.context.ResponseWriter;
+import javax.faces.render.RenderKit;
+import javax.faces.render.Renderer;
+import javax.faces.render.ResponseStateManager;
+
+import org.apache.shale.renderer.ValidatorCommandRenderer;
+import org.apache.shale.renderer.ValidatorInputRenderer;
+
+/**
+ * <p>Decorates the original <code>RenderKit</code> passed by the
+ * overloaded constructor in the {@link org.apache.shale.view.faces.ViewViewHandler}.
+ * The majority of the implementation is passed on the the original
+ * <code>RenderKit</code> but requests for renderers registered with the
+ * "javax.faces.Command" and "javax.faces.Input" families are decorated.
+ * The wrapper adds special behavior for the
+ * {@link org.apache.shale.validator.CommonsValidator} validator and
+ * {@link org.apache.shale.component.ValidatorScript} component.</p>
+ */
+public class ValidatorRenderKit extends RenderKit {
+
+ private static String COMMAND_FAMILY = "javax.faces.Command";
+ private static String INPUT_FAMILY = "javax.faces.Input";
+
+
+ /**
+ * <p>The original RenderKit.</p>
+ */
+ private RenderKit defaultRenderKit = null;
+ /**
+ * <p>This constructor is overloaded to pass the original
+ * <code>RenderKit</p>.
+ */
+ public ValidatorRenderKit(RenderKit defaultRenderKit) {
+ this.defaultRenderKit = defaultRenderKit;
+ }
+
+ // Specified by default RenderKit
+ public void addRenderer(String componentFamily, String rendererType, Renderer renderer) {
+ this.defaultRenderKit.addRenderer(componentFamily, rendererType, renderer);
+ }
+
+ /**
+ * <p>If the component family is not "javax.faces.Command" or
+ * "javax.faces.Input", the <code>defaultRenderKit</code> handles the
+ * request. If the family is "javax.faces.Command", the default
+ * renderer is decorated with {@link org.apache.shale.renderer.ValidatorCommandRenderer}.
+ * If the component family is "javax.faces.Input", the default
+ * renderer is decorated with {@link org.apache.shale.renderer.ValidatorInputRenderer}.
+ */
+ public Renderer getRenderer(String componentFamily, String rendererType) {
+ Renderer target = defaultRenderKit.getRenderer(componentFamily, rendererType);
+ if (componentFamily.equals(COMMAND_FAMILY)) {
+ if (!(target instanceof ValidatorCommandRenderer)) {
+ target = new ValidatorCommandRenderer(target);
+ addRenderer(componentFamily, rendererType, target);
+ }
+ } else if (componentFamily.equals(INPUT_FAMILY)) {
+ if (!(target instanceof ValidatorInputRenderer)) {
+ target = new ValidatorInputRenderer(target);
+ addRenderer(componentFamily, rendererType, target);
+ }
+ }
+
+ return target;
+ }
+
+ // Specified by default RenderKit
+ public ResponseStateManager getResponseStateManager() {
+ return defaultRenderKit.getResponseStateManager();
+ }
+
+ // Specified by default RenderKit
+ public ResponseWriter createResponseWriter(Writer writer,
+ String contentTypeList,
+ String characterEncoding){
+ return defaultRenderKit.createResponseWriter(writer, contentTypeList, characterEncoding);
+ }
+
+ // Specified by default RenderKit
+ public ResponseStream createResponseStream(OutputStream outputStream) {
+ return defaultRenderKit.createResponseStream(outputStream);
+ }
+
+}
Propchange: struts/shale/trunk/core-library/src/java/org/apache/shale/faces/ValidatorRenderKit.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: struts/shale/trunk/core-library/src/java/org/apache/shale/faces/ValidatorRenderKit.java
------------------------------------------------------------------------------
svn:keywords = date author id rev
Added: struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorCommandRenderer.java
URL: http://svn.apache.org/viewcvs/struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorCommandRenderer.java?rev=391884&view=auto
==============================================================================
--- struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorCommandRenderer.java (added)
+++ struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorCommandRenderer.java Wed Apr 5 19:46:34 2006
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.shale.renderer;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import javax.faces.component.UICommand;
+import javax.faces.component.UIComponent;
+import javax.faces.context.FacesContext;
+import javax.faces.context.ResponseWriter;
+import javax.faces.convert.ConverterException;
+import javax.faces.render.Renderer;
+
+/**
+ * This renderer is a hybrid renderer decorator that is dynamically
+ * registered by the {@link org.apache.shale.faces.ValidatorRenderKit}
+ * for component renderers in the "javax.faces.Command" family.</p>
+ */
+public class ValidatorCommandRenderer extends Renderer {
+
+ /**
+ * <p>The Original Renderer.</p>
+ */
+ private Renderer defaultRenderer = null;
+
+ /**
+ * <p>The overloaded constructor is passed the original
+ * <code>Renderer</code> for the family and component type.</p>
+ */
+ public ValidatorCommandRenderer(Renderer defaultRenderer) {
+ this.defaultRenderer = defaultRenderer;
+ }
+
+
+ /**
+ * <p>Attribute name used to override the default behavior of how the immediate
+ * attribute effects the execution of client side javascript validation.</p>
+ */
+ public static final String OVERRIDE_IMMEDIATE = "org.apache.shale.validator.immediate";
+
+ private static final int ENCODE_BEGIN = 0;
+
+ private static final int ENCODE_CHILDREN = 1;
+
+ private static final int ENCODE_END = 2;
+
+ /**
+ * <b>Interrogates the component's immediate property and the component's
+ * immediate override attribute to determine if client side validation is
+ * invoked. If either the property or attribute override is false, client
+ * side validation is invoked. Otherwise, the response writer is hijacked
+ * and the original render is invoked. The result is buffered and a
+ * statement of javascript is injected into the onclick event which cancels
+ * client side validation. The original response writer is restored and the
+ * modified markup is written to the response writer. The
+ * <code>encodeSwitch</code> determines if the encodeBegin, encodeChildren
+ * or encodeEnd methods should be invoked on the decorated renderer.</b>
+ */
+ protected void encode(FacesContext context, UIComponent component,
+ int encodeSwitch) throws IOException {
+
+ UICommand command = (UICommand) component;
+
+ // look for a override to the default
+ boolean immediateOverride = true;
+ String attr = (String) component.getAttributes()
+ .get(OVERRIDE_IMMEDIATE);
+ if (attr != null) {
+ immediateOverride = Boolean.valueOf(attr).booleanValue();
+ }
+
+ if (command.isImmediate() && immediateOverride) {
+
+ ResponseWriter hijackedWriter = context.getResponseWriter();
+ // builds a buffer to write the page to
+ StringWriter writer = new StringWriter();
+ // create a buffered response writer
+ ResponseWriter buffResponsewriter = context.getRenderKit()
+ .createResponseWriter(writer, null,
+ hijackedWriter.getCharacterEncoding());
+ // push buffered writer to the faces context
+ context.setResponseWriter(buffResponsewriter);
+
+ if (encodeSwitch == ENCODE_BEGIN)
+ defaultRenderer.encodeBegin(context, component);
+ else if (encodeSwitch == ENCODE_CHILDREN)
+ defaultRenderer.encodeChildren(context, component);
+ else
+ defaultRenderer.encodeEnd(context, component);
+
+ buffResponsewriter.write(' ');
+ buffResponsewriter.flush();
+ buffResponsewriter.close();
+ writer.flush();
+ writer.close();
+ StringBuffer buff = writer.getBuffer();
+ int i = buff.indexOf("onclick=\"");
+ if (i > 0) {
+ buff.insert(i + "onclick=\"".length(), "bCancel=true;");
+ }
+
+ hijackedWriter.write(buff.toString());
+ context.setResponseWriter(hijackedWriter);
+
+ } else {
+ if (encodeSwitch == ENCODE_BEGIN)
+ defaultRenderer.encodeBegin(context, component);
+ else if (encodeSwitch == ENCODE_CHILDREN)
+ defaultRenderer.encodeChildren(context, component);
+ else
+ defaultRenderer.encodeEnd(context, component);
+ }
+ }
+
+ // Specified by original Renderer
+ public String convertClientId(FacesContext context, String id) {
+ return defaultRenderer.convertClientId(context, id);
+ }
+
+
+ // Specified by original Renderer
+ public Object getConvertedValue(FacesContext context, UIComponent component, Object o) throws ConverterException {
+ return defaultRenderer.getConvertedValue(context, component, o);
+ }
+
+ // Specified by original Renderer
+ public void decode(FacesContext context, UIComponent component) {
+ defaultRenderer.decode(context, component);
+ }
+
+ /**
+ * <p>
+ * Invokes the <code>encode</code> method passing
+ * <code>ENCODE_BEGIN</code> for the encodeSwitch parameter.
+ * </p>
+ */
+ public void encodeBegin(FacesContext context, UIComponent component)
+ throws IOException {
+ encode(context, component, ENCODE_BEGIN);
+ }
+
+ /**
+ * <p>Invokes the <code>encode</code> method passing
+ * <code>ENCODE_CHILDREN</code> for the encodeSwitch parameter.</p>
+ */
+ public void encodeChildren(FacesContext context, UIComponent component)
+ throws IOException {
+ encode(context, component, ENCODE_CHILDREN);
+ }
+
+ /**
+ * <p>Invokes the <code>encode</code> method passing <code>ENCODE_END</code>
+ * for the encodeSwitch parameter.</p>
+ */
+ public void encodeEnd(FacesContext context, UIComponent component)
+ throws IOException {
+ encode(context, component, ENCODE_END);
+ }
+
+ // Specified by original Renderer
+ public boolean getRendersChildren() {
+ return defaultRenderer.getRendersChildren();
+ }
+
+}
Propchange: struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorCommandRenderer.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorCommandRenderer.java
------------------------------------------------------------------------------
svn:keywords = date author id rev
Added: struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorInputRenderer.java
URL: http://svn.apache.org/viewcvs/struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorInputRenderer.java?rev=391884&view=auto
==============================================================================
--- struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorInputRenderer.java (added)
+++ struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorInputRenderer.java Wed Apr 5 19:46:34 2006
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.shale.renderer;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javax.faces.component.UIComponent;
+import javax.faces.context.FacesContext;
+import javax.faces.convert.ConverterException;
+import javax.faces.render.Renderer;
+
+/**
+ * This renderer is a hybrid renderer decorator that is dynamically
+ * registered by the {@link org.apache.shale.faces.ValidatorRenderKit}
+ * for component renderers in the "javax.faces.Input" family.</p>
+ */
+public class ValidatorInputRenderer extends Renderer {
+
+ private Renderer defaultRenderer = null;
+
+ /**
+ * <p>This constant is the name of a reserved attribute that will hold
+ * a <code>Set</code> of clientId's for the component.</p>
+ */
+ public static final String VALIDATOR_CLIENTIDS_ATTR = "org.apache.shale.validator.clientIdSet";
+
+ /**
+ * <p>Overloaded constructor is passed the original
+ * <code>Renderer</code>.</p>
+ */
+ public ValidatorInputRenderer(Renderer defaultRenderer) {
+ this.defaultRenderer = defaultRenderer;
+ }
+
+ // Specified by original Renderer
+ public String convertClientId(FacesContext context, String id) {
+ return defaultRenderer.convertClientId(context, id);
+ }
+
+ // Specified by original Renderer
+ public void decode(FacesContext context, UIComponent component) {
+ defaultRenderer.decode(context, component);
+ }
+
+
+ /**
+ * <p>This override captures the clientId of the target component before
+ * passing on to the original renderer. The clientId is added to a Set
+ * that is used by the {@link org.apache.shale.component.ValidatorScript}
+ * component for adding client side JavaScript validation. This hook is
+ * needed when the {@link org.apache.shale.validator.CommonsValidator}
+ * is added to a UIData subclass. The components in this class are not
+ * unique per row so the clientId can only be captured during the rendering
+ * process.</p>
+ */
+ public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
+
+ Set ids = (Set) component.getAttributes().get(VALIDATOR_CLIENTIDS_ATTR);
+ if (ids == null) {
+ ids = new TreeSet();
+ component.getAttributes().put(VALIDATOR_CLIENTIDS_ATTR, ids);
+ }
+
+ ids.add(component.getClientId(context));
+ defaultRenderer.encodeBegin(context, component);
+ }
+
+ // Specified by original Renderer
+ public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
+ defaultRenderer.encodeChildren(context, component);
+ }
+
+ // Specified by original Renderer
+ public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
+ defaultRenderer.encodeEnd(context, component);
+ }
+
+ // Specified by original Renderer
+ public Object getConvertedValue(FacesContext context, UIComponent component, Object value) throws ConverterException {
+ return defaultRenderer.getConvertedValue(context, component, value);
+ }
+
+ // Specified by original Renderer
+ public boolean getRendersChildren() {
+ return defaultRenderer.getRendersChildren();
+ }
+
+}
Propchange: struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorInputRenderer.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: struts/shale/trunk/core-library/src/java/org/apache/shale/renderer/ValidatorInputRenderer.java
------------------------------------------------------------------------------
svn:keywords = date author id rev
Modified: struts/shale/trunk/core-library/src/java/org/apache/shale/view/faces/ViewViewHandler.java
URL: http://svn.apache.org/viewcvs/struts/shale/trunk/core-library/src/java/org/apache/shale/view/faces/ViewViewHandler.java?rev=391884&r1=391883&r2=391884&view=diff
==============================================================================
--- struts/shale/trunk/core-library/src/java/org/apache/shale/view/faces/ViewViewHandler.java (original)
+++ struts/shale/trunk/core-library/src/java/org/apache/shale/view/faces/ViewViewHandler.java Wed Apr 5 19:46:34 2006
@@ -23,15 +23,18 @@
import java.util.Map;
import javax.faces.FacesException;
+import javax.faces.FactoryFinder;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.el.EvaluationException;
-import javax.faces.el.ValueBinding;
import javax.faces.el.VariableResolver;
+import javax.faces.render.RenderKit;
+import javax.faces.render.RenderKitFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.shale.faces.ValidatorRenderKit;
import org.apache.shale.util.Messages;
import org.apache.shale.view.Constants;
import org.apache.shale.view.ViewController;
@@ -124,6 +127,7 @@
public UIViewRoot createView(FacesContext context, String viewId) {
UIViewRoot view = original.createView(context, viewId);
setupViewController(context, view, viewId, false);
+ setupRenderKit(context, view);
return view;
}
@@ -300,5 +304,18 @@
}
+ /**
+ * <p>The default RenderKit is decorated with {@link org.apache.shale.faces.ValidatorRenderKit}.
+ * This wrapper intercepts component renderer's decorating them.</p>
+ *
+ * @param context <code>FacesContext</code> for the current request
+ * @param view <code>UIViewRoot</code> for the current component tree
+ */
+ private void setupRenderKit(FacesContext context, UIViewRoot view) {
+ RenderKitFactory factory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
+ RenderKit defaultRenderKit = factory.getRenderKit(context, view.getRenderKitId());
+ if (!(defaultRenderKit instanceof ValidatorRenderKit))
+ factory.addRenderKit(view.getRenderKitId(), new ValidatorRenderKit(defaultRenderKit));
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@struts.apache.org
For additional commands, e-mail: dev-help@struts.apache.org