You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jmeter-dev@jakarta.apache.org by se...@apache.org on 2010/02/02 01:55:10 UTC
svn commit: r905484 [2/2] - in /jakarta/jmeter/trunk: bin/ bin/testfiles/
src/protocol/http/org/apache/jmeter/protocol/http/sampler/ xdocs/
Modified: jakarta/jmeter/trunk/bin/testfiles/SimpleTestPlan.jmx
URL: http://svn.apache.org/viewvc/jakarta/jmeter/trunk/bin/testfiles/SimpleTestPlan.jmx?rev=905484&r1=905483&r2=905484&view=diff
==============================================================================
--- jakarta/jmeter/trunk/bin/testfiles/SimpleTestPlan.jmx (original)
+++ jakarta/jmeter/trunk/bin/testfiles/SimpleTestPlan.jmx Tue Feb 2 00:55:09 2010
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<jmeterTestPlan version="1.2" properties="1.8">
+<jmeterTestPlan version="1.2" properties="2.1">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="TestPlan" enabled="true">
<collectionProp name="TestPlan.thread_groups"/>
@@ -18,8 +18,8 @@
<stringProp name="ThreadGroup.num_threads">1</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="LoopController" enabled="true">
- <stringProp name="LoopController.loops">1</stringProp>
<boolProp name="LoopController.continue_forever">false</boolProp>
+ <stringProp name="LoopController.loops">1</stringProp>
</elementProp>
<longProp name="ThreadGroup.end_time">0</longProp>
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
@@ -39,40 +39,34 @@
<GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="Ant Pages" enabled="true"/>
<hashTree>
<HTTPSampler guiclass="HttpTestSampleGui" testclass="HTTPSampler" testname="Home Page" enabled="true">
+ <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
+ <collectionProp name="Arguments.arguments"/>
+ </elementProp>
<stringProp name="HTTPSampler.path">/ant/index.html</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.use_keepalive">false</boolProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<boolProp name="HTTPSampler.image_parser">false</boolProp>
<boolProp name="HTTPSampler.follow_redirects">false</boolProp>
- <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
- <collectionProp name="Arguments.arguments"/>
- </elementProp>
<stringProp name="HTTPSampler.port"></stringProp>
- <stringProp name="HTTPSampler.mimetype"></stringProp>
- <stringProp name="HTTPSampler.FILE_FIELD"></stringProp>
<stringProp name="HTTPSampler.monitor">false</stringProp>
<stringProp name="HTTPSampler.domain"></stringProp>
- <stringProp name="HTTPSampler.FILE_NAME"></stringProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
</HTTPSampler>
<hashTree/>
<HTTPSampler guiclass="HttpTestSampleGui" testclass="HTTPSampler" testname="News Page" enabled="true">
+ <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
+ <collectionProp name="Arguments.arguments"/>
+ </elementProp>
<stringProp name="HTTPSampler.path">/ant/antnews.html</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.use_keepalive">false</boolProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<boolProp name="HTTPSampler.image_parser">false</boolProp>
<boolProp name="HTTPSampler.follow_redirects">false</boolProp>
- <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
- <collectionProp name="Arguments.arguments"/>
- </elementProp>
<stringProp name="HTTPSampler.port"></stringProp>
- <stringProp name="HTTPSampler.mimetype"></stringProp>
- <stringProp name="HTTPSampler.FILE_FIELD"></stringProp>
<stringProp name="HTTPSampler.monitor">false</stringProp>
<stringProp name="HTTPSampler.domain"></stringProp>
- <stringProp name="HTTPSampler.FILE_NAME"></stringProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
</HTTPSampler>
<hashTree/>
@@ -80,46 +74,42 @@
<GenericController guiclass="LogicControllerGui" testclass="GenericController" testname="Log4J Pages" enabled="true"/>
<hashTree>
<HTTPSampler guiclass="HttpTestSampleGui" testclass="HTTPSampler" testname="Home Page" enabled="true">
+ <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
+ <collectionProp name="Arguments.arguments"/>
+ </elementProp>
<stringProp name="HTTPSampler.path">/log4j/index.html</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.use_keepalive">false</boolProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<boolProp name="HTTPSampler.image_parser">false</boolProp>
<boolProp name="HTTPSampler.follow_redirects">false</boolProp>
- <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
- <collectionProp name="Arguments.arguments"/>
- </elementProp>
<stringProp name="HTTPSampler.port"></stringProp>
- <stringProp name="HTTPSampler.mimetype"></stringProp>
- <stringProp name="HTTPSampler.FILE_FIELD"></stringProp>
<stringProp name="HTTPSampler.monitor">false</stringProp>
<stringProp name="HTTPSampler.domain"></stringProp>
- <stringProp name="HTTPSampler.FILE_NAME"></stringProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
</HTTPSampler>
<hashTree/>
<HTTPSampler guiclass="HttpTestSampleGui" testclass="HTTPSampler" testname="History Page" enabled="true">
+ <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
+ <collectionProp name="Arguments.arguments"/>
+ </elementProp>
<stringProp name="HTTPSampler.path">/log4j/docs/history.html</stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.use_keepalive">false</boolProp>
<stringProp name="HTTPSampler.protocol">http</stringProp>
<boolProp name="HTTPSampler.image_parser">false</boolProp>
<boolProp name="HTTPSampler.follow_redirects">false</boolProp>
- <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
- <collectionProp name="Arguments.arguments"/>
- </elementProp>
<stringProp name="HTTPSampler.port"></stringProp>
- <stringProp name="HTTPSampler.mimetype"></stringProp>
- <stringProp name="HTTPSampler.FILE_FIELD"></stringProp>
<stringProp name="HTTPSampler.monitor">false</stringProp>
<stringProp name="HTTPSampler.domain"></stringProp>
- <stringProp name="HTTPSampler.FILE_NAME"></stringProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
</HTTPSampler>
<hashTree/>
</hashTree>
<ResultCollector guiclass="GraphVisualizer" testclass="ResultCollector" testname="File Reporter" enabled="true">
+ <boolProp name="ResultCollector.error_logging">false</boolProp>
<objProp>
+ <name>saveConfig</name>
<value class="SampleSaveConfiguration">
<time>true</time>
<latency>true</latency>
@@ -143,10 +133,8 @@
<saveAssertionResultsFailureMessage>false</saveAssertionResultsFailureMessage>
<assertionsResultsToSave>0</assertionsResultsToSave>
</value>
- <name>saveConfig</name>
</objProp>
<stringProp name="filename">simple-test.dat</stringProp>
- <boolProp name="ResultCollector.error_logging">false</boolProp>
</ResultCollector>
<hashTree/>
</hashTree>
Modified: jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java
URL: http://svn.apache.org/viewvc/jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java?rev=905484&r1=905483&r2=905484&view=diff
==============================================================================
--- jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java (original)
+++ jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBase.java Tue Feb 2 00:55:09 2010
@@ -149,26 +149,7 @@
public static final String DO_MULTIPART_POST = "HTTPSampler.DO_MULTIPART_POST"; // $NON-NLS-1$
- /*
- * JMeter 2.3.1 and earlier only had fields for one file on the GUI:
- * - FILE_NAME
- * - FILE_FIELD
- * - MIMETYPE
- * These were stored in their own individual properties.
- *
- * Version 2.3.3 introduced a list of files, each with their own path, name and mimetype.
- *
- * In order to maintain backwards compatibility of test plans, the 3 original properties
- * have been retained; additional file entries are stored in an HTTPFileArgs class.
- * The HTTPFileArgs class is only present if there is more than 1 file; this means that
- * such test plans are backward compatible.
- *
- * The original get methods still work, but have been deprecated;
- * the user is expected to retrieve the list of files using the getHTTPFiles() method.
- *
- * In order to speed up processing, the class maintains a copy of the current
- * files in the fileList variable.
- */
+ // @see mergeFileProperties
// Must be private, as the file list needs special handling
private final static String FILE_ARGS = "HTTPsampler.Files"; // $NON-NLS-1$
// MIMETYPE is kept for backward compatibility with old test plans
@@ -254,9 +235,6 @@
private boolean dynamicPath = false;// Set false if spaces are already encoded
- // must be kept in synch with the various file properties
- private transient HTTPFileArg[] fileList;
- // The code assumes that this will be accessed from a single thread only
////////////////////// Code ///////////////////////////
@@ -265,94 +243,6 @@
}
/**
- * The name parameter to be applied to the file
- * @deprecated use setHTTPFiles() instead
- */
- @Deprecated
- public void setFileField(String value) {
- fileList = null; // Force rebuild
- setFileFieldProperty(value);
- }
-
- private void setFileFieldProperty(String value) {
- if (value == null) {
- throw new IllegalArgumentException("Value must not be null");
- }
- setProperty(FILE_FIELD, value);
- }
-
- /**
- * The name parameter to be applied to the file
- * @deprecated Use getHTTPFiles() array instead
- */
- @Deprecated
- public String getFileField() {
- checkCount("getFileField");
- return getPropertyAsString(FILE_FIELD);
- }
-
- /**
- * The actual name of the file to POST
- * @deprecated use setHTTPFiles() instead
- */
- @Deprecated
- public void setFilename(String value) {
- fileList = null; // Force rebuild
- setFilenameProperty(value);
- }
-
- private void setFilenameProperty(String value) {
- if (value == null) {
- throw new IllegalArgumentException("Value must not be null");
- }
- setProperty(FILE_NAME, value);
- }
-
- /**
- * The actual name of the file to POST
- * @deprecated Use getHTTPFiles() array instead
- */
- @Deprecated
- public String getFilename() {
- checkCount("getFilename");
- return getPropertyAsString(FILE_NAME);
- }
-
- /**
- * Set the files mime type
- * @deprecated use setHTTPFiles() instead
- * @param value
- */
- @Deprecated
- public void setMimetype(String value) {
- fileList = null; // Force rebuild
- setMimetypeProperty(value);
- }
-
- private void setMimetypeProperty(String value) {
- if (value == null) {
- throw new IllegalArgumentException("Value must not be null");
- }
- setProperty(MIMETYPE, value);
- }
-
- /**
- * @deprecated Use getHTTPFiles() array instead
- */
- @Deprecated
- public String getMimetype() {
- checkCount("getMimetype");
- return getPropertyAsString(MIMETYPE);
- }
-
- private void checkCount(String method) {
- if (getHTTPFileCount() > 1) {
- log.warn(method + "() called with more than 1 file; additional files may be ignored.");
- }
-
- }
-
- /**
* Determine if the file should be sent as the entire Post body,
* i.e. without any additional wrapping
*
@@ -1317,7 +1207,6 @@
public Object clone() {
HTTPSamplerBase base = (HTTPSamplerBase) super.clone();
base.dynamicPath = dynamicPath;
- base.fileList = null;
return base;
}
@@ -1475,7 +1364,11 @@
* HTTPFileArgs object that stores file list to be uploaded.
*/
private void setHTTPFileArgs(HTTPFileArgs value) {
- setProperty(new TestElementProperty(FILE_ARGS, value));
+ if (value.getHTTPFileArgCount() > 0){
+ setProperty(new TestElementProperty(FILE_ARGS, value));
+ } else {
+ removeProperty(FILE_ARGS); // no point saving an empty list
+ }
}
/*
@@ -1485,15 +1378,6 @@
return (HTTPFileArgs) getProperty(FILE_ARGS).getObjectValue();
}
- /*
- * Method to clear the additional files list to be uploaded.
- *
- * HTTPFileArgs object that stores file list to be uploaded.
- */
- private void clearHTTPFileArgs() {
- removeProperty(FILE_ARGS);
- }
-
/**
* Get the collection of files as a list.
* The list is built up from the filename/filefield/mimetype properties,
@@ -1504,34 +1388,8 @@
* @return an array of file arguments (never null)
*/
public HTTPFileArg[] getHTTPFiles() {
- if (fileList != null){
- return fileList;
- }
- HTTPFileArg[] outFiles;
- // Check for original data names
- // Use properties so variables and functions are not resolved too early
- JMeterProperty fileName = getProperty(FILE_NAME);
- JMeterProperty paramName = getProperty(FILE_FIELD);
- JMeterProperty mimeType = getProperty(MIMETYPE);
- HTTPFileArg file = new HTTPFileArg(fileName, paramName, mimeType);
- if(file.isNotEmpty()) {
- // Now deal with any additional file arguments
- final HTTPFileArgs fileArgs = getHTTPFileArgs();
- if(fileArgs != null) {
- outFiles = new HTTPFileArg[1+fileArgs.getHTTPFileArgCount()];
- outFiles[0] = file; // first file
- HTTPFileArg[] infiles = fileArgs.asArray();
- for (int i = 0; i < infiles.length; i++){
- outFiles[i+1] = infiles[i];
- }
- } else {
- outFiles = new HTTPFileArg[]{file}; // just one file
- }
- } else {
- outFiles = new HTTPFileArg[]{}; // no files, empty array
- }
- fileList = outFiles; // update the list cache
- return fileList;
+ final HTTPFileArgs fileArgs = getHTTPFileArgs();
+ return fileArgs == null ? new HTTPFileArg[] {} : fileArgs.asArray();
}
public int getHTTPFileCount(){
@@ -1545,45 +1403,17 @@
* @param files list of files to save
*/
public void setHTTPFiles(HTTPFileArg[] files) {
- clearHTTPFileArgs();
- fileList = null; // it will be regenerated by get
- // First weed out the empty files
- HTTPFileArg[] nonEmptyFile = {};
- int filesFound = 0;
+ HTTPFileArgs fileArgs = new HTTPFileArgs();
+ // Weed out the empty files
if (files.length > 0) {
- nonEmptyFile = new HTTPFileArg[files.length];
for(int i=0; i < files.length; i++){
HTTPFileArg file = files[i];
if (file.isNotEmpty()){
- nonEmptyFile[filesFound++] = file;
+ fileArgs.addHTTPFileArg(file);
}
}
}
- // Any files left?
- if (filesFound > 0){
- HTTPFileArg file = nonEmptyFile[0];
- setFilenameProperty(file.getPath());
- setFileFieldProperty(file.getParamName());
- setMimetypeProperty(file.getMimeType());
- if (filesFound > 1){
- HTTPFileArgs fileArgs = new HTTPFileArgs();
- boolean empty=true;
- for(int i=1; i < filesFound; i++){
- final HTTPFileArg fileArg = nonEmptyFile[i];
- if (fileArg.isNotEmpty()){
- fileArgs.addHTTPFileArg(fileArg);
- empty=false;
- }
- }
- if (!empty){
- setHTTPFileArgs(fileArgs);
- }
- }
- } else {
- setFilenameProperty("");
- setFileFieldProperty("");
- setMimetypeProperty("");
- }
+ setHTTPFileArgs(fileArgs);
}
public static String[] getValidMethodsAsArray(){
@@ -1666,5 +1496,54 @@
w.close();
return w.toByteArray();
}
+
+ /**
+ * JMeter 2.3.1 and earlier only had fields for one file on the GUI:
+ * - FILE_NAME
+ * - FILE_FIELD
+ * - MIMETYPE
+ * These were stored in their own individual properties.
+ *
+ * Version 2.3.3 introduced a list of files, each with their own path, name and mimetype.
+ *
+ * In order to maintain backwards compatibility of test plans, the 3 original properties
+ * were retained; additional file entries are stored in an HTTPFileArgs class.
+ * The HTTPFileArgs class was only present if there is more than 1 file; this means that
+ * such test plans are backward compatible.
+ *
+ * Versions after 2.3.4 dispense with the original set of 3 properties.
+ * Test plans that use them are converted to use a single HTTPFileArgs list.
+ *
+ * @see HTTPSamplerBaseConverter
+ */
+ void mergeFileProperties() {
+ JMeterProperty fileName = getProperty(FILE_NAME);
+ JMeterProperty paramName = getProperty(FILE_FIELD);
+ JMeterProperty mimeType = getProperty(MIMETYPE);
+ HTTPFileArg oldStyleFile = new HTTPFileArg(fileName, paramName, mimeType);
+
+ HTTPFileArgs fileArgs = getHTTPFileArgs();
+
+ HTTPFileArgs allFileArgs = new HTTPFileArgs();
+ if(oldStyleFile.isNotEmpty()) { // OK, we have an old-style file definition
+ allFileArgs.addHTTPFileArg(oldStyleFile); // save it
+ // Now deal with any additional file arguments
+ if(fileArgs != null) {
+ HTTPFileArg[] infiles = fileArgs.asArray();
+ for (int i = 0; i < infiles.length; i++){
+ allFileArgs.addHTTPFileArg(infiles[i]);
+ }
+ }
+ } else {
+ if(fileArgs != null) { // for new test plans that don't have FILE/PARAM/MIME properties
+ allFileArgs = fileArgs;
+ }
+ }
+ // Updated the property lists
+ setHTTPFileArgs(allFileArgs);
+ removeProperty(FILE_FIELD);
+ removeProperty(FILE_NAME);
+ removeProperty(MIMETYPE);
+ }
}
Added: jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java
URL: http://svn.apache.org/viewvc/jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java?rev=905484&view=auto
==============================================================================
--- jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java (added)
+++ jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java Tue Feb 2 00:55:09 2010
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ *
+ */
+
+/*
+ * Created on Sep 14, 2004
+ *
+ */
+package org.apache.jmeter.protocol.http.sampler;
+
+import org.apache.jmeter.save.converters.TestElementConverter;
+
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.mapper.Mapper;
+
+/**
+ * Class for XStream conversion of HTTPResult
+ *
+ */
+public class HTTPSamplerBaseConverter extends TestElementConverter {
+
+ /**
+ * Returns the converter version; used to check for possible
+ * incompatibilities
+ */
+ public static String getVersion() {
+ return "$Revision$"; //$NON-NLS-1$
+ }
+
+ public HTTPSamplerBaseConverter(Mapper arg0) {
+ super(arg0);
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("unchecked") // superclass does not support types
+ @Override
+ public boolean canConvert(Class arg0) {
+ return HTTPSamplerBase.class.isAssignableFrom(arg0);
+ }
+
+ /**
+ * Override TestElementConverter; convert HTTPSamplerBase to merge
+ * the two means of providing file names into a single list.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+ final HTTPSamplerBase httpSampler = (HTTPSamplerBase) super.unmarshal(reader, context);
+ httpSampler.mergeFileProperties();
+ return httpSampler;
+ }
+}
\ No newline at end of file
Propchange: jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jakarta/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/sampler/HTTPSamplerBaseConverter.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Modified: jakarta/jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jakarta/jmeter/trunk/xdocs/changes.xml?rev=905484&r1=905483&r2=905484&view=diff
==============================================================================
--- jakarta/jmeter/trunk/xdocs/changes.xml (original)
+++ jakarta/jmeter/trunk/xdocs/changes.xml Tue Feb 2 00:55:09 2010
@@ -61,6 +61,7 @@
<p>
The Avalon file format for JMX and JTL files is no longer supported.
+Any such files will need to be converted by reading them in JMeter 2.3.4 and resaving them.
</p>
<p>
@@ -77,6 +78,7 @@
<li>Bug 48542 - SoapSampler uses wrong response header field to decide if response is gzip encoded</li>
<li>Bug 48568 - CookieManager broken for AjpSampler</li>
<li>Bug 48570 - AjpSampler doesn't support query parameters (GET/POST)</li>
+<li>Bug 46901 - HTTP Sampler does not process var/func refs correctly in first file parameter</li>
</ul>
<h3>Other Samplers</h3>
---------------------------------------------------------------------
To unsubscribe, e-mail: jmeter-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: jmeter-dev-help@jakarta.apache.org