You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-dev@logging.apache.org by ce...@apache.org on 2004/03/31 21:02:15 UTC

cvs commit: logging-log4j/tests/input/joran parser2.xml

ceki        2004/03/31 11:02:14

  Modified:    src/java/org/apache/joran ExecutionContext.java
               src/java/org/apache/joran/action NestComponentIA.java
                        Action.java
               tests    build.xml
               tests/input/joran parser2.xml
  Added:       src/java/org/apache/joran Interpreter.java
               tests/src/java/org/apache/joran InterpreterTest.java
  Removed:     src/java/org/apache/joran JoranParser.java
               tests/src/java/org/apache/joran JoranParserTest.java
  Log:
  
  - Renamed JoranParser to Interpreter.
  
  - Implicit actions now work for SAX based element handling.
  
  Revision  Changes    Path
  1.4       +3 -3      logging-log4j/src/java/org/apache/joran/ExecutionContext.java
  
  Index: ExecutionContext.java
  ===================================================================
  RCS file: /home/cvs/logging-log4j/src/java/org/apache/joran/ExecutionContext.java,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- ExecutionContext.java	27 Feb 2004 16:47:28 -0000	1.3
  +++ ExecutionContext.java	31 Mar 2004 19:02:14 -0000	1.4
  @@ -40,9 +40,9 @@
   	Vector errorList;
   	Properties substProperties;
   	
  -	JoranParser joranParser;
  +	Interpreter joranParser;
       
  -	public ExecutionContext(JoranParser joranParser) {
  +	public ExecutionContext(Interpreter joranParser) {
   		this.joranParser = joranParser;
   		objectStack = new Stack();
   		objectMap = new HashMap(5);
  @@ -57,7 +57,7 @@
       return errorList;
     }
   
  -  public JoranParser getJoranParser() {
  +  public Interpreter getJoranParser() {
       return joranParser;
     }
   
  
  
  
  1.1                  logging-log4j/src/java/org/apache/joran/Interpreter.java
  
  Index: Interpreter.java
  ===================================================================
  /*
   * Copyright 1999,2004 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.joran;
  
  import org.apache.joran.action.*;
  
  import org.apache.log4j.Logger;
  
  import org.xml.sax.Attributes;
  import org.xml.sax.Locator;
  import org.xml.sax.helpers.DefaultHandler;
  
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Stack;
  import java.util.Vector;
  
  
  public class Interpreter extends DefaultHandler {
    static final Logger logger = Logger.getLogger(Interpreter.class);
    private static List EMPTY_LIST = new Vector(0);
    
    private RuleStore ruleStore;
    private ExecutionContext ec;
    private ArrayList implicitActions;
    Pattern pattern;
    Locator locator;
    /**
     * The <code>actionListStack</code> contains a list of actions that are 
     * executing for the given XML element.
     * 
     * A list of actions is pushed by the {link #startElement} and popped by
     * {@link #endElement}. 
     * 
     */
    Stack actionListStack;
    
    Interpreter(RuleStore rs) {
      ruleStore = rs;
      ec = new ExecutionContext(this);
      implicitActions = new ArrayList(3);
      pattern = new Pattern();
      actionListStack = new Stack();
    }
  
    public ExecutionContext getExecutionContext() {
      return ec;
    }
  
    public void startDocument() {
      System.out.println(" in JP startDocument");
    }
  
    public void startElement(
      String namespaceURI, String localName, String qName, Attributes atts) {
      String x = null;
   
      String tagName = getTagName(localName, qName);
  
      logger.debug("in startElement <" + tagName + ">");
        
      pattern.push(tagName);
  
      List applicableActionList = getapplicableActionList(pattern);
  
      if (applicableActionList != null) {
        actionListStack.add(applicableActionList);
        callBeginAction(applicableActionList, tagName, atts);
      } else {
        actionListStack.add(EMPTY_LIST);
        logger.debug("no applicable action for <"+tagName+">.");
      }
    }
  
    public void endElement(String namespaceURI, String localName, String qName) {
      List applicableActionList = (List) actionListStack.pop();
  
      if (applicableActionList != EMPTY_LIST) {
        callEndAction(applicableActionList, getTagName(localName, qName));
      }
  
      // given that we always push, we must also pop the pattern
      pattern.pop();
    }
  
  
    public Locator getDocumentLocator() {
      return locator;
    }
    public void setDocumentLocator(Locator l) {
      locator = l;
    }
    
    String getTagName(String localName, String qName) {
      String tagName = localName;
  
      if ((tagName == null) || (tagName.length() < 1)) {
        tagName = qName;
      }
  
      return tagName;
    }
  
    public void addImplcitAction(ImplicitAction ia) {
      implicitActions.add(ia);
    }
  
    /**
     * Check if any implicit actions are applicable. As soon as an applicable
     * action is found, it is returned. Thus, the returned list will have at most
     * one element.
     */
    List lookupImplicitAction(ExecutionContext ec, Pattern pattern) {
      int len = implicitActions.size();
  
      for (int i = 0; i < len; i++) {
        ImplicitAction ia = (ImplicitAction) implicitActions.get(i);
  
        if (ia.isApplicable(ec, pattern.peekLast())) {
          List actionList = new ArrayList(1);
          actionList.add(ia);
  
          return actionList;
        }
      }
  
      return null;
    }
  
    /**
     * Return the list of applicable patterns for this
    */
    List getapplicableActionList(Pattern pattern) {
      List applicableActionList = ruleStore.matchActions(pattern);
  
      //logger.debug("set of applicable patterns: " + applicableActionList);
      if (applicableActionList == null) {
        applicableActionList = lookupImplicitAction(ec, pattern);
      }
  
      return applicableActionList;
    }
  
    void callBeginAction(
      List applicableActionList, String tagName, Attributes atts) {
      if (applicableActionList == null) {
        return;
      }
  
      Iterator i = applicableActionList.iterator();
  
      while (i.hasNext()) {
        Action action = (Action) i.next();
        action.begin(ec, tagName, atts);
      }
    }
  
    void callEndAction(List applicableActionList, String tagName) {
      if (applicableActionList == null) {
        return;
      }
  
      //logger.debug("About to call end actions on node: <" + localName + ">");
      Iterator i = applicableActionList.iterator();
  
      while (i.hasNext()) {
        Action action = (Action) i.next();
        action.end(ec, tagName);
      }
    }
  
    public RuleStore getRuleStore() {
      return ruleStore;
    }
  
    public void setRuleStore(RuleStore ruleStore) {
      this.ruleStore = ruleStore;
    }
  }
  
  
  
  1.1                  logging-log4j/tests/src/java/org/apache/joran/InterpreterTest.java
  
  Index: InterpreterTest.java
  ===================================================================
  /*
   * Copyright 1999,2004 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.
   */
  
  /*
   * Created on Aug 24, 2003
   *
   * To change the template for this generated file go to
   * Window>Preferences>Java>Code Generation>Code and Comments
   */
  package org.apache.joran;
  
  import junit.framework.Test;
  import junit.framework.TestCase;
  import junit.framework.TestSuite;
  
  import org.apache.joran.action.NestComponentIA;
  import org.apache.joran.action.NewRuleAction;
  import org.apache.joran.action.ParamAction;
  import org.apache.joran.action.StackCounterAction;
  
  import org.apache.log4j.Appender;
  import org.apache.log4j.ConsoleAppender;
  import org.apache.log4j.FileAppender;
  import org.apache.log4j.Level;
  import org.apache.log4j.LogManager;
  import org.apache.log4j.Logger;
  import org.apache.log4j.PatternLayout;
  import org.apache.log4j.joran.action.ActionConst;
  import org.apache.log4j.joran.action.AppenderAction;
  import org.apache.log4j.joran.action.AppenderRefAction;
  import org.apache.log4j.joran.action.ConversionRuleAction;
  import org.apache.log4j.joran.action.LayoutAction;
  import org.apache.log4j.joran.action.LevelAction;
  import org.apache.log4j.joran.action.LoggerAction;
  import org.apache.log4j.joran.action.RootLoggerAction;
  
  import java.util.HashMap;
  import java.util.Stack;
  
  import javax.xml.parsers.SAXParser;
  import javax.xml.parsers.SAXParserFactory;
  
  
  /**
   * @author ceki
   *
   * To change the template for this generated type comment go to
   * Window>Preferences>Java>Code Generation>Code and Comments
   */
  public class InterpreterTest extends TestCase {
    static final Logger logger = Logger.getLogger(InterpreterTest.class);
  
    /**
     * Constructor for JoranParserTestCase.
     * @param name
     */
    public InterpreterTest(String name) {
      super(name);
    }
  
    /*
     * @see TestCase#setUp()
     */
    protected void setUp() throws Exception {
      super.setUp();
  
      Logger root = Logger.getRootLogger();
      root.addAppender(
        new ConsoleAppender(new PatternLayout("%r %5p [%t] %c - %m%n")));
      
    }
  
    /*
     * @see TestCase#tearDown()
     */
    protected void tearDown() throws Exception {
      super.tearDown();
      LogManager.shutdown();
    }
  
    SAXParser createParser() throws Exception {
      SAXParserFactory spf = SAXParserFactory.newInstance();
      return spf.newSAXParser();
    }
    
    /** 
     * Tests the basic looping contruct in Interpreter.
     * 
     * The parser is set up to push 2 string objects for each element encountered.
     * The results are compared with a witness stack.
     */
    public void testBasicLoop() throws Exception {
      
      RuleStore rs = new SimpleRuleStore();
      rs.addRule(
          new Pattern("log4j:configuration"), new StackCounterAction());
      rs.addRule(
          new Pattern("log4j:configuration/root"), new StackCounterAction());
      rs.addRule(
        new Pattern("log4j:configuration/root/level"), new StackCounterAction());
  
      Interpreter jp = new Interpreter(rs);
      ExecutionContext ec = jp.getExecutionContext();
      SAXParser saxParser = createParser();
      saxParser.parse("file:input/joran/basicLoop.xml", jp);
      
      Stack witness = new Stack();
      witness.push("log4j:configuration-begin");
      witness.push("root-begin");
      witness.push("level-begin");
      witness.push("level-end");
      witness.push("root-end");
      witness.push("log4j:configuration-end");
      assertEquals(witness, ec.getObjectStack());
    }
    
    /**
     * This test verifies that <logger>, <root> and embedded <level> elements
     * are handled correctly.  
     */
    public void testParsing1() throws Exception {
      logger.debug("Starting testLoop");
  
      RuleStore rs = new SimpleRuleStore();
      logger.debug("pattern: " + new Pattern("log4j:configuration/logger"));
      rs.addRule(new Pattern("log4j:configuration/logger"), new LoggerAction());
      rs.addRule(
        new Pattern("log4j:configuration/logger/level"), new LevelAction());
      rs.addRule(
        new Pattern("log4j:configuration/root"), new RootLoggerAction());
      rs.addRule(
          new Pattern("log4j:configuration/root/level"), new LevelAction());
  
      Interpreter jp = new Interpreter(rs);
      ExecutionContext ec = jp.getExecutionContext();
      HashMap omap = ec.getObjectMap();
      omap.put(ActionConst.APPENDER_BAG, new HashMap());
      ec.pushObject(LogManager.getLoggerRepository());
      SAXParser saxParser = createParser();
      saxParser.parse("file:input/joran/parser1.xml", jp);
      
      Logger rootLogger = LogManager.getLoggerRepository().getRootLogger();
      assertSame(Level.WARN, rootLogger.getLevel());
   
      Logger asdLogger = LogManager.getLoggerRepository().getLogger("asd");
      assertSame(Level.DEBUG, asdLogger.getLevel());
   
      assertEquals(2, ec.getErrorList().size());
      String e0 = (String) ec.getErrorList().get(0);
      if(!e0.startsWith("No 'name' attribute in element")) {
        fail("Expected error string [No 'name' attribute in element]");
      }
      String e1 = (String) ec.getErrorList().get(1);
      if(!e1.startsWith("For element <level>")) {
        fail("Expected error string [For element <level>]");
      }
    }
  
    /**
     * This tests verifies the handling of logger, logger/level, root, root/level
     * logger/appender-ref, root/appender-ref, appender, appender/layout,
     * and param actions.
     * 
     * These cover a fairly significant part of log4j configuration directives.
     * 
     * */
    public void testParsing2() throws Exception {
      logger.debug("Starting testLoop2");
      RuleStore rs = new SimpleRuleStore();
      rs.addRule(new Pattern("log4j:configuration/logger"), new LoggerAction());
      rs.addRule(
        new Pattern("log4j:configuration/logger/level"), new LevelAction());
      rs.addRule(
        new Pattern("log4j:configuration/root"), new RootLoggerAction());
      rs.addRule(
        new Pattern("log4j:configuration/root/level"), new LevelAction());
      rs.addRule(
        new Pattern("log4j:configuration/logger/appender-ref"),
        new AppenderRefAction());
      rs.addRule(
        new Pattern("log4j:configuration/root/appender-ref"),
        new AppenderRefAction());
      rs.addRule(
        new Pattern("log4j:configuration/appender"), new AppenderAction());
      rs.addRule(
        new Pattern("log4j:configuration/appender/layout"), new LayoutAction());
      rs.addRule(new Pattern("*/param"), new ParamAction());
  
      Interpreter jp = new Interpreter(rs);
      ExecutionContext ec = jp.getExecutionContext();
      HashMap omap = ec.getObjectMap();
      omap.put(ActionConst.APPENDER_BAG, new HashMap());
      ec.pushObject(LogManager.getLoggerRepository());
      SAXParser saxParser = createParser();
      saxParser.parse("file:input/joran/parser2.xml", jp);
  
      // the following assertions depend on the contensts of parser2.xml
      Logger rootLogger = LogManager.getLoggerRepository().getRootLogger();
      assertSame(Level.DEBUG, rootLogger.getLevel());
   
      Logger asdLogger = LogManager.getLoggerRepository().getLogger("asd");
      assertSame(Level.INFO, asdLogger.getLevel());
      
      FileAppender a1Back = (FileAppender) asdLogger.getAppender("A1");  
      assertFalse("a1.append should be false", a1Back.getAppend());
      assertEquals("output/temp.A1", a1Back.getFile());
      PatternLayout plBack = (PatternLayout) a1Back.getLayout();
      assertEquals("%-5p %c{2} - %m%n", plBack.getConversionPattern());
      
      a1Back = (FileAppender) rootLogger.getAppender("A1");  
      
      assertEquals(3, ec.getErrorList().size());
      String e0 = (String) ec.getErrorList().get(0);
      if(!e0.startsWith("No 'name' attribute in element")) {
        fail("Expected error string [No 'name' attribute in element]");
      }
      String e1 = (String) ec.getErrorList().get(1);
      if(!e1.startsWith("For element <level>")) {
        fail("Expected error string [For element <level>]");
      }
      String e2 = (String) ec.getErrorList().get(2);
      if(!e2.startsWith("Could not find an AppenderAttachable at the top of execution stack. Near")) {
        fail("Expected error string [Could not find an AppenderAttachable at the top of execution stack. Near]");
      }
      
    }
  
    public void testParsing3() throws Exception {
      logger.debug("Starting testLoop3");
  
      RuleStore rs = new SimpleRuleStore();
      rs.addRule(new Pattern("log4j:configuration/logger"), new LoggerAction());
      rs.addRule(
        new Pattern("log4j:configuration/logger/level"), new LevelAction());
      rs.addRule(
        new Pattern("log4j:configuration/root"), new RootLoggerAction());
  
      //rs.addRule(
      //new Pattern("log4j:configuration/root/level"), new LevelAction());
      rs.addRule(
        new Pattern("log4j:configuration/logger/appender-ref"),
        new AppenderRefAction());
      rs.addRule(
        new Pattern("log4j:configuration/root/appender-ref"),
        new AppenderRefAction());
      rs.addRule(
        new Pattern("log4j:configuration/appender"), new AppenderAction());
      rs.addRule(
        new Pattern("log4j:configuration/appender/layout"), new LayoutAction());
      rs.addRule(new Pattern("*/param"), new ParamAction());
  
      Interpreter jp = new Interpreter(rs);
      jp.addImplcitAction(new NestComponentIA());
  
      ExecutionContext ec = jp.getExecutionContext();
      HashMap omap = ec.getObjectMap();
      omap.put(ActionConst.APPENDER_BAG, new HashMap());
      ec.pushObject(LogManager.getLoggerRepository());
      logger.debug("About to parse doc");
     
      SAXParser saxParser = createParser();
      saxParser.parse("file:input/joran/parser3.xml", jp);
  
      // the following assertions depend on the contensts of parser3.xml
    }
  
    public void testNewConversionWord() throws Exception {
      logger.debug("Starting testNewConversionWord");
  
      RuleStore rs = new SimpleRuleStore();
      rs.addRule(
        new Pattern("log4j:configuration/appender"), new AppenderAction());
      rs.addRule(
        new Pattern("log4j:configuration/appender/layout"), new LayoutAction());
      rs.addRule(
        new Pattern("log4j:configuration/appender/layout/conversionRule"),
        new ConversionRuleAction());
  
      rs.addRule(new Pattern("*/param"), new ParamAction());
  
      Interpreter jp = new Interpreter(rs);
      jp.addImplcitAction(new NestComponentIA());
  
      ExecutionContext ec = jp.getExecutionContext();
      HashMap omap = ec.getObjectMap();
      omap.put(ActionConst.APPENDER_BAG, new HashMap());
      ec.pushObject(LogManager.getLoggerRepository());
  
      SAXParser saxParser = createParser();
      saxParser.parse("file:input/joran/conversionRule.xml", jp);
  
      HashMap appenderBag =
        (HashMap) ec.getObjectMap().get(ActionConst.APPENDER_BAG);
      Appender appender = (Appender) appenderBag.get("A1");
      PatternLayout pl = (PatternLayout) appender.getLayout();
      assertEquals("org.apache.log4j.toto", pl.getRuleRegistry().get("toto"));
    }
    
    public void testNewRule1() throws Exception {
      logger.debug("Starting testNewConversionWord");
    
      RuleStore rs = new SimpleRuleStore();
      rs.addRule(
        new Pattern("log4j:configuration/newRule"),
        new NewRuleAction());
  
      Interpreter jp = new Interpreter(rs);
      ExecutionContext ec = jp.getExecutionContext();
      HashMap omap = ec.getObjectMap();
      omap.put(ActionConst.APPENDER_BAG, new HashMap());
      ec.pushObject(LogManager.getLoggerRepository());
  
      SAXParser saxParser = createParser();
      saxParser.parse("file:input/joran/newRule1.xml", jp);
  
      String str = (String) ec.getObjectMap().get("hello");
      assertEquals("Hello John Doe.", str);
    }
    
    public static Test suite() {
      TestSuite suite = new TestSuite();
      //suite.addTest(new InterpreterTest("testBasicLoop"));
      //suite.addTest(new InterpreterTest("testParsing1"));
      //suite.addTest(new InterpreterTest("testParsing2"));
      suite.addTest(new InterpreterTest("testParsing3"));
      return suite;
    }
  
  }
  
  
  
  1.7       +2 -0      logging-log4j/src/java/org/apache/joran/action/NestComponentIA.java
  
  Index: NestComponentIA.java
  ===================================================================
  RCS file: /home/cvs/logging-log4j/src/java/org/apache/joran/action/NestComponentIA.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- NestComponentIA.java	31 Mar 2004 13:13:15 -0000	1.6
  +++ NestComponentIA.java	31 Mar 2004 19:02:14 -0000	1.7
  @@ -92,6 +92,8 @@
     }
   
     public void end(ExecutionContext ec, String tagName) {
  +    
  +    logger.debug("entering end method");
       if (inError) {
           return;
         }
  
  
  
  1.9       +3 -3      logging-log4j/src/java/org/apache/joran/action/Action.java
  
  Index: Action.java
  ===================================================================
  RCS file: /home/cvs/logging-log4j/src/java/org/apache/joran/action/Action.java,v
  retrieving revision 1.8
  retrieving revision 1.9
  diff -u -r1.8 -r1.9
  --- Action.java	31 Mar 2004 13:13:15 -0000	1.8
  +++ Action.java	31 Mar 2004 19:02:14 -0000	1.9
  @@ -17,7 +17,7 @@
   package org.apache.joran.action;
   
   import org.apache.joran.ExecutionContext;
  -import org.apache.joran.JoranParser;
  +import org.apache.joran.Interpreter;
   import org.xml.sax.Attributes;
   import org.xml.sax.Locator;
   
  @@ -66,7 +66,7 @@
     }
     
     protected int getColumnNumber(ExecutionContext ec) {
  -    JoranParser jp = ec.getJoranParser();
  +    Interpreter jp = ec.getJoranParser();
       Locator locator = jp.getDocumentLocator();
       if(locator != null) {
         return locator.getColumnNumber();
  @@ -75,7 +75,7 @@
     }
     
     protected int getLineNumber(ExecutionContext ec) {
  -    JoranParser jp = ec.getJoranParser();
  +    Interpreter jp = ec.getJoranParser();
       Locator locator = jp.getDocumentLocator();
       if(locator != null) {
         return locator.getLineNumber();
  
  
  
  1.48      +3 -3      logging-log4j/tests/build.xml
  
  Index: build.xml
  ===================================================================
  RCS file: /home/cvs/logging-log4j/tests/build.xml,v
  retrieving revision 1.47
  retrieving revision 1.48
  diff -u -r1.47 -r1.48
  --- build.xml	27 Mar 2004 06:46:27 -0000	1.47
  +++ build.xml	31 Mar 2004 19:02:14 -0000	1.48
  @@ -133,7 +133,7 @@
     <!-- ================================================================= -->
     <!-- Joran unit tests                                                 -->
     <!-- ================================================================= -->
  -  <target name="Joran" depends="Pattern, SimpleStore, JoranParser"/>
  +  <target name="Joran" depends="Pattern, SimpleStore, Interpreter"/>
   
   
     <!-- ================================================================= -->
  @@ -453,11 +453,11 @@
     </target>
   
   
  -  <target name="JoranParser" depends="build, cleanOutputDir">
  +  <target name="Interpreter" depends="build, cleanOutputDir">
       <junit printsummary="yes" fork="yes" haltonfailure="yes">
         <classpath refid="tests.classpath"/>
         <formatter type="plain" usefile="false" />
  -      <test name="org.apache.joran.JoranParserTest" />
  +      <test name="org.apache.joran.InterpreterTest" />
       </junit>
     </target>
     
  
  
  
  1.3       +1 -1      logging-log4j/tests/input/joran/parser2.xml
  
  Index: parser2.xml
  ===================================================================
  RCS file: /home/cvs/logging-log4j/tests/input/joran/parser2.xml,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- parser2.xml	31 Mar 2004 14:04:08 -0000	1.2
  +++ parser2.xml	31 Mar 2004 19:02:14 -0000	1.3
  @@ -1,7 +1,7 @@
   <?xml version="1.0" encoding="UTF-8" ?>
   <!DOCTYPE log4j:configuration>
   
  -<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
  +<log4j:configuration>
   
     <appender name="A1" class="org.apache.log4j.FileAppender">
       asdfasdf
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
For additional commands, e-mail: log4j-dev-help@logging.apache.org