You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by kk...@apache.org on 2011/11/10 07:24:40 UTC
svn commit: r1200182 - in
/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf: MessageBytes.java
StringCache.java UDecoder.java UEncoder.java
Author: kkolinko
Date: Thu Nov 10 06:24:38 2011
New Revision: 1200182
URL: http://svn.apache.org/viewvc?rev=1200182&view=rev
Log:
Merged revision 1187753 from tomcat/trunk:
Clean-up. No functional change.
Part 8.
Modified:
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/MessageBytes.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/StringCache.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/UDecoder.java
tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/UEncoder.java
Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/MessageBytes.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/MessageBytes.java?rev=1200182&r1=1200181&r2=1200182&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/MessageBytes.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/MessageBytes.java Thu Nov 10 06:24:38 2011
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.tomcat.util.buf;
import java.io.IOException;
@@ -45,20 +44,20 @@ public final class MessageBytes implemen
was a String */
public static final int T_STR = 1;
/** getType() is T_STR if the the object used to create the MessageBytes
- was a byte[] */
+ was a byte[] */
public static final int T_BYTES = 2;
/** getType() is T_STR if the the object used to create the MessageBytes
- was a char[] */
+ was a char[] */
public static final int T_CHARS = 3;
private int hashCode=0;
- // did we computed the hashcode ?
+ // did we computed the hashcode ?
private boolean hasHashCode=false;
// Internal objects to represent array + offset, and specific methods
- private ByteChunk byteC=new ByteChunk();
- private CharChunk charC=new CharChunk();
-
+ private final ByteChunk byteC=new ByteChunk();
+ private final CharChunk charC=new CharChunk();
+
// String
private String strValue;
// true if a String value was computed. Probably not needed,
@@ -92,7 +91,7 @@ public final class MessageBytes implemen
return byteC.isNull() && charC.isNull() && ! hasStrValue;
// bytes==null && strValue==null;
}
-
+
/**
* Resets the message bytes to an uninitialized (NULL) state.
*/
@@ -139,7 +138,7 @@ public final class MessageBytes implemen
byteC.setCharset(charset);
}
- /**
+ /**
* Sets the content to be a char[]
*
* @param c the bytes
@@ -155,7 +154,7 @@ public final class MessageBytes implemen
hasLongValue=false;
}
- /**
+ /**
* Set the content to be a string
*/
public void setString( String s ) {
@@ -178,8 +177,10 @@ public final class MessageBytes implemen
*/
@Override
public String toString() {
- if( hasStrValue ) return strValue;
-
+ if( hasStrValue ) {
+ return strValue;
+ }
+
switch (type) {
case T_CHARS:
strValue=charC.toString();
@@ -200,7 +201,7 @@ public final class MessageBytes implemen
public int getType() {
return type;
}
-
+
/**
* Returns the byte chunk, representing the byte[] and offset/length.
* Valid only if T_BYTES or after a conversion was made.
@@ -252,7 +253,7 @@ public final class MessageBytes implemen
char cc[]=strValue.toCharArray();
charC.setChars(cc, 0, cc.length);
}
-
+
/**
* Returns the length of the original buffer.
@@ -260,15 +261,19 @@ public final class MessageBytes implemen
* in chars.
*/
public int getLength() {
- if(type==T_BYTES)
+ if(type==T_BYTES) {
return byteC.getLength();
+ }
if(type==T_CHARS) {
return charC.getLength();
}
- if(type==T_STR)
+ if(type==T_STR) {
return strValue.length();
+ }
toString();
- if( strValue==null ) return 0;
+ if( strValue==null ) {
+ return 0;
+ }
return strValue.length();
}
@@ -282,7 +287,9 @@ public final class MessageBytes implemen
public boolean equals(String s) {
switch (type) {
case T_STR:
- if (strValue == null) return s == null;
+ if (strValue == null) {
+ return s == null;
+ }
return strValue.equals( s );
case T_CHARS:
return charC.equals( s );
@@ -301,7 +308,9 @@ public final class MessageBytes implemen
public boolean equalsIgnoreCase(String s) {
switch (type) {
case T_STR:
- if (strValue == null) return s == null;
+ if (strValue == null) {
+ return s == null;
+ }
return strValue.equalsIgnoreCase( s );
case T_CHARS:
return charC.equalsIgnoreCase( s );
@@ -335,10 +344,10 @@ public final class MessageBytes implemen
// mb is either CHARS or BYTES.
// this is either CHARS or BYTES
// Deal with the 4 cases ( in fact 3, one is symmetric)
-
+
if( mb.type == T_CHARS && type==T_CHARS ) {
return charC.equals( mb.charC );
- }
+ }
if( mb.type==T_BYTES && type== T_BYTES ) {
return byteC.equals( mb.byteC );
}
@@ -352,7 +361,7 @@ public final class MessageBytes implemen
return true;
}
-
+
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
@@ -378,9 +387,13 @@ public final class MessageBytes implemen
public boolean startsWithIgnoreCase(String s, int pos) {
switch (type) {
case T_STR:
- if( strValue==null ) return false;
- if( strValue.length() < pos + s.length() ) return false;
-
+ if( strValue==null ) {
+ return false;
+ }
+ if( strValue.length() < pos + s.length() ) {
+ return false;
+ }
+
for( int i=0; i<s.length(); i++ ) {
if( Ascii.toLower( s.charAt( i ) ) !=
Ascii.toLower( strValue.charAt( pos + i ))) {
@@ -401,16 +414,18 @@ public final class MessageBytes implemen
// -------------------- Hash code --------------------
@Override
public int hashCode() {
- if( hasHashCode ) return hashCode;
+ if( hasHashCode ) {
+ return hashCode;
+ }
int code = 0;
- code=hash();
+ code=hash();
hashCode=code;
hasHashCode=true;
return code;
}
- // normal hash.
+ // normal hash.
private int hash() {
int code=0;
switch (type) {
@@ -439,20 +454,20 @@ public final class MessageBytes implemen
toString();
return strValue.indexOf( s, starting );
}
-
+
// Inefficient initial implementation. Will be replaced on the next
// round of tune-up
public int indexOf(String s) {
return indexOf( s, 0 );
}
-
+
public int indexOfIgnoreCase(String s, int starting) {
toString();
String upper=strValue.toUpperCase(Locale.ENGLISH);
String sU=s.toUpperCase(Locale.ENGLISH);
return upper.indexOf( sU, starting );
}
-
+
/**
* Returns true if the message bytes starts with the specified string.
* @param c the character
@@ -505,7 +520,7 @@ public final class MessageBytes implemen
private boolean hasIntValue=false;
private long longValue;
private boolean hasLongValue=false;
-
+
/** Set the buffer to the representation of an int
*/
public void setInt(int i) {
@@ -592,12 +607,13 @@ public final class MessageBytes implemen
// Used for headers conversion
/** Convert the buffer to an int, cache the value
- */
- public int getInt()
+ */
+ public int getInt()
{
- if( hasIntValue )
+ if( hasIntValue ) {
return intValue;
-
+ }
+
switch (type) {
case T_BYTES:
intValue=byteC.getInt();
@@ -611,11 +627,12 @@ public final class MessageBytes implemen
// Used for headers conversion
/** Convert the buffer to an long, cache the value
- */
+ */
public long getLong() {
- if( hasLongValue )
+ if( hasLongValue ) {
return longValue;
-
+ }
+
switch (type) {
case T_BYTES:
longValue=byteC.getLong();
@@ -630,13 +647,13 @@ public final class MessageBytes implemen
}
// -------------------- Future may be different --------------------
-
+
private static MessageBytesFactory factory=new MessageBytesFactory();
public static void setFactory( MessageBytesFactory mbf ) {
factory=mbf;
}
-
+
public static class MessageBytesFactory {
protected MessageBytesFactory() {
}
Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/StringCache.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/StringCache.java?rev=1200182&r1=1200181&r2=1200182&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/StringCache.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/StringCache.java Thu Nov 10 06:24:38 2011
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.tomcat.util.buf;
import java.nio.charset.Charset;
@@ -33,33 +32,33 @@ public class StringCache {
private static final org.apache.juli.logging.Log log=
org.apache.juli.logging.LogFactory.getLog( StringCache.class );
-
-
+
+
// ------------------------------------------------------- Static Variables
-
+
/**
* Enabled ?
*/
protected static boolean byteEnabled = ("true".equals(System.getProperty(
"tomcat.util.buf.StringCache.byte.enabled", "false")));
-
+
protected static boolean charEnabled = ("true".equals(System.getProperty(
"tomcat.util.buf.StringCache.char.enabled", "false")));
-
+
protected static int trainThreshold = Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.trainThreshold", "20000"));
-
+
protected static int cacheSize = Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.cacheSize", "200"));
-
+
protected static int maxStringSize = Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.maxStringSize", "128"));
-
+
/**
* Statistics hash map for byte chunk.
@@ -67,18 +66,18 @@ public class StringCache {
protected static HashMap<ByteEntry,int[]> bcStats =
new HashMap<ByteEntry,int[]>(cacheSize);
-
+
/**
* toString count for byte chunk.
*/
protected static int bcCount = 0;
-
-
+
+
/**
* Cache for byte chunk.
*/
protected static ByteEntry[] bcCache = null;
-
+
/**
* Statistics hash map for char chunk.
@@ -90,38 +89,38 @@ public class StringCache {
/**
* toString count for char chunk.
*/
- protected static int ccCount = 0;
-
+ protected static int ccCount = 0;
+
/**
* Cache for char chunk.
*/
protected static CharEntry[] ccCache = null;
-
+
/**
* Access count.
*/
protected static int accessCount = 0;
-
+
/**
* Hit count.
*/
protected static int hitCount = 0;
-
+
// ------------------------------------------------------------ Properties
-
+
/**
* @return Returns the cacheSize.
*/
public int getCacheSize() {
return cacheSize;
}
-
-
+
+
/**
* @param cacheSize The cacheSize to set.
*/
@@ -129,47 +128,47 @@ public class StringCache {
StringCache.cacheSize = cacheSize;
}
-
+
/**
* @return Returns the enabled.
*/
public boolean getByteEnabled() {
return byteEnabled;
}
-
-
+
+
/**
* @param byteEnabled The enabled to set.
*/
public void setByteEnabled(boolean byteEnabled) {
StringCache.byteEnabled = byteEnabled;
}
-
-
+
+
/**
* @return Returns the enabled.
*/
public boolean getCharEnabled() {
return charEnabled;
}
-
-
+
+
/**
* @param charEnabled The enabled to set.
*/
public void setCharEnabled(boolean charEnabled) {
StringCache.charEnabled = charEnabled;
}
-
-
+
+
/**
* @return Returns the trainThreshold.
*/
public int getTrainThreshold() {
return trainThreshold;
}
-
-
+
+
/**
* @param trainThreshold The trainThreshold to set.
*/
@@ -177,15 +176,15 @@ public class StringCache {
StringCache.trainThreshold = trainThreshold;
}
-
+
/**
* @return Returns the accessCount.
*/
public int getAccessCount() {
return accessCount;
}
-
-
+
+
/**
* @return Returns the hitCount.
*/
@@ -193,10 +192,10 @@ public class StringCache {
return hitCount;
}
-
+
// -------------------------------------------------- Public Static Methods
-
+
public void reset() {
hitCount = 0;
accessCount = 0;
@@ -209,8 +208,8 @@ public class StringCache {
ccCount = 0;
}
}
-
-
+
+
public static String toString(ByteChunk bc) {
// If the cache is null, then either caching is disabled, or we're
@@ -301,7 +300,7 @@ public class StringCache {
0, end - start);
// Set encoding
entry.charset = bc.getCharset();
- // Initialize occurrence count to one
+ // Initialize occurrence count to one
count = new int[1];
count[0] = 1;
// Set in the stats hash map
@@ -324,12 +323,12 @@ public class StringCache {
hitCount++;
return result;
}
-
+
}
public static String toString(CharChunk cc) {
-
+
// If the cache is null, then either caching is disabled, or we're
// still training
if (ccCache == null) {
@@ -416,7 +415,7 @@ public class StringCache {
entry.name = new char[cc.getLength()];
System.arraycopy(cc.getBuffer(), start, entry.name,
0, end - start);
- // Initialize occurrence count to one
+ // Initialize occurrence count to one
count = new int[1];
count[0] = 1;
// Set in the stats hash map
@@ -439,10 +438,10 @@ public class StringCache {
hitCount++;
return result;
}
-
+
}
-
-
+
+
// ----------------------------------------------------- Protected Methods
@@ -478,7 +477,7 @@ public class StringCache {
return result;
}
-
+
/**
* Find an entry given its name in the cache and return the associated
* String.
@@ -493,7 +492,7 @@ public class StringCache {
}
}
-
+
/**
* Find an entry given its name in a sorted array of map elements.
* This will return the index for the closest inferior or equal item in the
@@ -509,10 +508,10 @@ public class StringCache {
if (b == -1) {
return -1;
}
-
+
if (compare(name, array[0].name) < 0) {
return -1;
- }
+ }
if (b == 0) {
return 0;
}
@@ -573,7 +572,7 @@ public class StringCache {
return result;
}
-
+
/**
* Find an entry given its name in the cache and return the associated
* String.
@@ -587,7 +586,7 @@ public class StringCache {
}
}
-
+
/**
* Find an entry given its name in a sorted array of map elements.
* This will return the index for the closest inferior or equal item in the
@@ -603,10 +602,10 @@ public class StringCache {
if (b == -1) {
return -1;
}
-
+
if (compare(name, array[0].name) < 0 ) {
return -1;
- }
+ }
if (b == 0) {
return 0;
}
@@ -659,7 +658,7 @@ public class StringCache {
}
return false;
}
-
+
}
@@ -686,7 +685,7 @@ public class StringCache {
}
return false;
}
-
+
}
Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/UDecoder.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/UDecoder.java?rev=1200182&r1=1200181&r2=1200182&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/UDecoder.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/UDecoder.java Thu Nov 10 06:24:38 2011
@@ -14,26 +14,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.tomcat.util.buf;
import java.io.CharConversionException;
import java.io.IOException;
-/**
+/**
* All URL decoding happens here. This way we can reuse, review, optimize
* without adding complexity to the buffers.
*
* The conversion will modify the original buffer.
- *
+ *
* @author Costin Manolache
*/
public final class UDecoder {
-
- protected static final boolean ALLOW_ENCODED_SLASH =
+
+ protected static final boolean ALLOW_ENCODED_SLASH =
Boolean.valueOf(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false")).booleanValue();
- public UDecoder()
+ public UDecoder()
{
}
@@ -57,18 +56,23 @@ public final class UDecoder {
int idx= ByteChunk.indexOf( buff, start, end, '%' );
int idx2=-1;
- if( query )
+ if( query ) {
idx2= ByteChunk.indexOf( buff, start, end, '+' );
+ }
if( idx<0 && idx2<0 ) {
return;
}
// idx will be the smallest positive indexes ( first % or + )
- if( idx2 >= 0 && idx2 < idx ) idx=idx2;
- if( idx < 0 ) idx=idx2;
+ if( idx2 >= 0 && idx2 < idx ) {
+ idx=idx2;
+ }
+ if( idx < 0 ) {
+ idx=idx2;
+ }
boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
-
+
for( int j=idx; j<end; j++, idx++ ) {
if( buff[ j ] == '+' && query) {
buff[idx]= (byte)' ' ;
@@ -81,9 +85,10 @@ public final class UDecoder {
}
byte b1= buff[j+1];
byte b2=buff[j+2];
- if( !isHexDigit( b1 ) || ! isHexDigit(b2 ))
+ if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
throw new CharConversionException( "isHexDigit");
-
+ }
+
j+=2;
int res=x2c( b1, b2 );
if (noSlash && (res == '/')) {
@@ -94,7 +99,7 @@ public final class UDecoder {
}
mb.setEnd( idx );
-
+
return;
}
@@ -122,14 +127,19 @@ public final class UDecoder {
int idx= CharChunk.indexOf( buff, start, cend, '%' );
int idx2=-1;
- if( query )
+ if( query ) {
idx2= CharChunk.indexOf( buff, start, cend, '+' );
+ }
if( idx<0 && idx2<0 ) {
return;
}
-
- if( idx2 >= 0 && idx2 < idx ) idx=idx2;
- if( idx < 0 ) idx=idx2;
+
+ if( idx2 >= 0 && idx2 < idx ) {
+ idx=idx2;
+ }
+ if( idx < 0 ) {
+ idx=idx2;
+ }
for( int j=idx; j<cend; j++, idx++ ) {
if( buff[ j ] == '+' && query ) {
@@ -144,9 +154,10 @@ public final class UDecoder {
}
char b1= buff[j+1];
char b2=buff[j+2];
- if( !isHexDigit( b1 ) || ! isHexDigit(b2 ))
+ if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
throw new CharConversionException("isHexDigit");
-
+ }
+
j+=2;
int res=x2c( b1, b2 );
buff[idx]=(char)res;
@@ -169,11 +180,13 @@ public final class UDecoder {
public void convert(MessageBytes mb, boolean query)
throws IOException
{
-
+
switch (mb.getType()) {
case MessageBytes.T_STR:
String strValue=mb.toString();
- if( strValue==null ) return;
+ if( strValue==null ) {
+ return;
+ }
mb.setString( convert( strValue, query ));
break;
case MessageBytes.T_CHARS:
@@ -188,7 +201,7 @@ public final class UDecoder {
}
// XXX Old code, needs to be replaced !!!!
- //
+ //
public final String convert(String str)
{
return convert(str, true);
@@ -196,11 +209,14 @@ public final class UDecoder {
public final String convert(String str, boolean query)
{
- if (str == null) return null;
-
- if( (!query || str.indexOf( '+' ) < 0) && str.indexOf( '%' ) < 0 )
+ if (str == null) {
+ return null;
+ }
+
+ if( (!query || str.indexOf( '+' ) < 0) && str.indexOf( '%' ) < 0 ) {
return str;
-
+ }
+
StringBuilder dec = new StringBuilder(); // decoded string output
int strPos = 0;
int strLen = str.length();
@@ -254,7 +270,7 @@ public final class UDecoder {
( c>='a' && c<='f' ) ||
( c>='A' && c<='F' ));
}
-
+
private static int x2c( byte b1, byte b2 ) {
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
(b1 -'0');
Modified: tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/UEncoder.java
URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/UEncoder.java?rev=1200182&r1=1200181&r2=1200182&view=diff
==============================================================================
--- tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/UEncoder.java (original)
+++ tomcat/tc7.0.x/trunk/java/org/apache/tomcat/util/buf/UEncoder.java Thu Nov 10 06:24:38 2011
@@ -14,7 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.tomcat.util.buf;
import java.io.CharArrayWriter;
@@ -26,7 +25,7 @@ import java.util.BitSet;
* This class is not thread safe - you need one encoder per thread.
* The encoder will save and recycle the internal objects, avoiding
* garbage.
- *
+ *
* You can add extra characters that you want preserved, for example
* while encoding a URL you can add "/".
*
@@ -36,7 +35,7 @@ public final class UEncoder {
private static final org.apache.juli.logging.Log log=
org.apache.juli.logging.LogFactory.getLog(UEncoder.class );
-
+
// Not static - the set may differ ( it's better than adding
// an extra check for "/", "+", etc
private BitSet safeChars=null;
@@ -44,7 +43,7 @@ public final class UEncoder {
private ByteChunk bb=null;
private String encoding="UTF8";
-
+
public UEncoder() {
initSafeChars();
}
@@ -74,22 +73,25 @@ public final class UEncoder {
for (int i = 0; i < s.length(); i++) {
int c = s.charAt(i);
if( safeChars.get( c ) ) {
- if(log.isDebugEnabled())
+ if(log.isDebugEnabled()) {
log.debug("Encoder: Safe: " + (char)c);
+ }
buf.write((char)c);
} else {
- if(log.isDebugEnabled())
+ if(log.isDebugEnabled()) {
log.debug("Encoder: Unsafe: " + (char)c);
+ }
c2b.convert( (char)c );
-
+
// "surrogate" - UTF is _not_ 16 bit, but 21 !!!!
// ( while UCS is 31 ). Amazing...
if (c >= 0xD800 && c <= 0xDBFF) {
if ( (i+1) < s.length()) {
int d = s.charAt(i+1);
if (d >= 0xDC00 && d <= 0xDFFF) {
- if(log.isDebugEnabled())
+ if(log.isDebugEnabled()) {
log.debug("Encoder: Unsafe: " + c);
+ }
c2b.convert( (char)d);
i++;
}
@@ -97,7 +99,7 @@ public final class UEncoder {
}
c2b.flushBuffer();
-
+
urlEncode( buf, bb.getBuffer(), bb.getOffset(),
bb.getLength() );
bb.recycle();
@@ -112,16 +114,18 @@ public final class UEncoder {
for( int j=off; j< len; j++ ) {
buf.write( '%' );
char ch = Character.forDigit((bytes[j] >> 4) & 0xF, 16);
- if(log.isDebugEnabled())
+ if(log.isDebugEnabled()) {
log.debug("Encoder: Encode: " + ch);
+ }
buf.write(ch);
ch = Character.forDigit(bytes[j] & 0xF, 16);
- if(log.isDebugEnabled())
+ if(log.isDebugEnabled()) {
log.debug("Encoder: Encode: " + ch);
+ }
buf.write(ch);
}
}
-
+
/**
* Utility function to re-encode the URL.
* Still has problems with charset, since UEncoder mostly
@@ -138,10 +142,10 @@ public final class UEncoder {
}
return outUri;
}
-
+
// -------------------- Internal implementation --------------------
-
+
private void initSafeChars() {
safeChars=new BitSet(128);
int i;
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org