sylvain     01/07/24 02:34:08

  Modified:    src/org/apache/cocoon/servlet CocoonServlet.java
  Added:       src/org/apache/cocoon/util/log CocoonLogFormatter.java
                        ExtensiblePatternFormatter.java package.html
  Log:
  New log formatter : outputs request URI and caller class name in the log file
  
  Revision  Changes    Path
  1.23      +14 -4     xml-cocoon2/src/org/apache/cocoon/servlet/CocoonServlet.java
  
  Index: CocoonServlet.java
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/org/apache/cocoon/servlet/CocoonServlet.java,v
  retrieving revision 1.22
  retrieving revision 1.23
  diff -u -r1.22 -r1.23
  --- CocoonServlet.java        2001/07/20 09:03:29     1.22
  +++ CocoonServlet.java        2001/07/24 09:34:08     1.23
  @@ -42,12 +42,13 @@
   import org.apache.cocoon.util.ClassUtils;
   import org.apache.cocoon.util.IOUtils;
   import org.apache.cocoon.util.NetUtils;
  +import org.apache.cocoon.util.log.CocoonLogFormatter;
  +import org.apache.log.ContextStack;
   import org.apache.log.Hierarchy;
   import org.apache.log.LogTarget;
   import org.apache.log.Logger;
   import org.apache.log.Priority;
   import org.apache.log.filter.PriorityFilter;
  -import org.apache.log.format.AvalonFormatter;
   import org.apache.log.output.FileOutputLogTarget;
   import org.apache.log.output.ServletOutputLogTarget;
   import org.xml.sax.SAXException;
  @@ -61,7 +62,7 @@
    * @author <a href="mailto:[EMAIL PROTECTED]";>Nicola Ken Barozzi</a> Aisa
    * @author <a href="mailto:[EMAIL PROTECTED]";>Berin Loritsch</a>
    * @author <a href="mailto:[EMAIL PROTECTED]";>Carsten Ziegeler</a>
  - * @version CVS $Revision: 1.22 $ $Date: 2001/07/20 09:03:29 $
  + * @version CVS $Revision: 1.23 $ $Date: 2001/07/24 09:34:08 $
    */
   
   public class CocoonServlet extends HttpServlet {
  @@ -334,9 +335,9 @@
               this.appContext.put(Constants.CONTEXT_LOG_FILE, logName);
   
               final String path = logDir + logName;
  -            final AvalonFormatter formatter = new AvalonFormatter();
  +            final CocoonLogFormatter formatter = new CocoonLogFormatter();
               formatter.setFormat( "%7.7{priority} %5.5{time}   [%8.8{category}] " +
  -                                 "(%{context}): %{message}\\n%{throwable}" );
  +                                 "(%{uri}) %{thread}/%{class:short}: 
%{message}\\n%{throwable}" );
   
               this.log = Hierarchy.getDefaultHierarchy().getLoggerFor("cocoon");
               this.log.setPriority(logPriority);
  @@ -485,6 +486,15 @@
               }
   
               Environment env = this.getEnvironment(uri, request, res);
  +
  +            // Initialize a fresh log context containing the object model : it
  +            // will be used by the CocoonLogFormatter
  +            ContextStack ctxStack = org.apache.log.ContextStack.getCurrentContext();
  +            ctxStack.clear();
  +            // Add thread name (default content for empty context)
  +            ctxStack.push(Thread.currentThread().getName());
  +            // Add the object model
  +            ctxStack.push(env.getObjectModel());
   
               if (!this.cocoon.process(env)) {
   
  
  
  
  1.1                  
xml-cocoon2/src/org/apache/cocoon/util/log/CocoonLogFormatter.java
  
  Index: CocoonLogFormatter.java
  ===================================================================
  /*****************************************************************************
   * Copyright (C) The Apache Software Foundation. All rights reserved.        *
   * ------------------------------------------------------------------------- *
   * This software is published under the terms of the Apache Software License *
   * version 1.1, a copy of which has been included  with this distribution in *
   * the LICENSE file.                                                         *
   *****************************************************************************/
  
  package org.apache.cocoon.util.log;
  
  import java.util.Map;
  
  import org.apache.avalon.framework.CascadingThrowable;
  
  import org.apache.cocoon.Constants;
  import org.apache.cocoon.environment.Request;
  
  import org.apache.log.ContextStack;
  import org.apache.log.LogEvent;
  
  /**
   * An extended pattern formatter. New patterns are defined by this class are :
   * <ul>
   * <li><code>class</code> : outputs the name of the class that has logged the
   *     message. The optional <code>short</code> subformat removes the
   *     package name. Warning : this pattern works only if formatting occurs in
   *     the same thread as the call to Logger, i.e. it won't work with
   *     <code>AsyncLogTarget</code>.</li>
   * <li><code>thread</code> : outputs the name of the current thread (first element
   *     on the context stack).</li>
   * <li><code>uri</code> : outputs the request URI.<li>
   * </ul>
   *
   * @author <a href="[EMAIL PROTECTED]">Sylvain Wallez</a>
   */
  
  public class CocoonLogFormatter extends ExtensiblePatternFormatter
  {
      protected final static int     TYPE_CLASS  = MAX_TYPE + 1;
      protected final static int     TYPE_URI    = MAX_TYPE + 2;
      protected final static int     TYPE_THREAD = MAX_TYPE + 3;
      
      protected final static String  TYPE_CLASS_STR       = "class";
      protected final static String  TYPE_CLASS_SHORT_STR = "short";
  
      protected final static String  TYPE_URI_STR         = "uri";
      protected final static String  TYPE_THREAD_STR      = "thread";
      
      
      /** 
       * Hack to get the call stack as an array of classes. The
       * SecurityManager class provides it as a protected method, so
       * change it to public through a new method !
       */
      static public class CallStack extends SecurityManager
      {
          /**
           * Returns the current execution stack as an array of classes.
           * The length of the array is the number of methods on the execution
           * stack. The element at index 0 is the class of the currently executing
           * method, the element at index 1 is the class of that method's caller,
           * and so on.
           */
  
          public Class[] get()
          {
              return getClassContext();
          }
      }
  
      /** The class that we will search for in the call stack */
      private Class loggerClass = org.apache.log.Logger.class;
      
      private CallStack callStack = new CallStack();
      
  
      protected int getTypeIdFor(String type) {
          
          // Search for new patterns defined here, or else delegate
          // to the parent class
          if (type.equalsIgnoreCase(TYPE_CLASS_STR))
              return TYPE_CLASS;
          else if (type.equalsIgnoreCase(TYPE_URI_STR))
              return TYPE_URI;
          else if (type.equalsIgnoreCase(TYPE_THREAD_STR))
              return TYPE_THREAD;
          else
              return super.getTypeIdFor( type );
      }
  
      protected String formatPatternRun(LogEvent event, PatternRun run) {
  
          // Format new patterns defined here, or else delegate to
          // the parent class
          switch (run.m_type) {
              case TYPE_CLASS :
                  return getClass(run.m_format);
                  
              case TYPE_URI :
                  return getURI(event.getContextStack());
                  
              case TYPE_THREAD :
                  return getThread(event.getContextStack());
          }
          
          return super.formatPatternRun(event, run);
      }
      
      /**
       * Finds the class that has called Logger.
       */
      private String getClass(String format) {
          
          Class[] stack = this.callStack.get();
          
          // Traverse the call stack in reverse order until we find a Logger
          for (int i = stack.length-1; i >= 0; i--) {
              if (this.loggerClass.isAssignableFrom(stack[i])) {
                  
                  // Found : the caller is the previous stack element
                  String className = stack[i+1].getName();
                  
                  // Handle optional format
                  if (TYPE_CLASS_SHORT_STR.equalsIgnoreCase(format))
                  {
                      int pos = className.lastIndexOf('.');
                      if (pos >= 0)
                          className = className.substring(pos + 1);
                  }
                  
                  return className;
              }
          }
          
          // No Logger found in call stack : can occur with AsyncLogTarget
          // where formatting takes place in a different thread.
          return "Unknown-class";
      }
      
      /**
       * Find the URI that is being processed.
       */
      
      private String getURI(ContextStack ctxStack) {
          String result = "Unknown-URI";
          
          // Get URI from the first context stack element, if it's a Map (the object 
model).
          if (ctxStack.getSize() > 1) {
              Object context = ctxStack.get(1);
              if (context instanceof Map) {
                  // Get the request
                  Request request = 
(Request)((Map)context).get(Constants.REQUEST_OBJECT);
                  if (request != null) {
                      result = request.getRequestURI();
                  }
              }
          }
          
          return result;
      }
      
      /**
       * Find the thread that is logged this event.
       */
      
      private String getThread(ContextStack ctxStack) {
          if (ctxStack.getSize() > 0)
              return String.valueOf(ctxStack.get(0));
          else
              return "Unknown-thread";
      }
      
      /**
       * Utility method to format stack trace so that CascadingExceptions are
       * formatted with all nested exceptions.
       *
       * FIXME : copied from AvalonFormatter, to be removed if 
ExtensiblePatternFormatter
       * replaces PatternFormatter.
       *
       * @param throwable the throwable instance
       * @param format ancilliary format parameter - allowed to be null
       * @return the formatted string
       */
      protected String getStackTrace( final Throwable throwable, final String format )
      {
          final StringBuffer sb = new StringBuffer();
          sb.append( super.getStackTrace( throwable, format ) );
  
          if( throwable instanceof CascadingThrowable )
          {
              final Throwable t = ((CascadingThrowable)throwable).getCause();
  
              sb.append( getStackTrace( t, format ) );
          }
  
          return sb.toString();
      }
  
  }
  
  
  
  1.1                  
xml-cocoon2/src/org/apache/cocoon/util/log/ExtensiblePatternFormatter.java
  
  Index: ExtensiblePatternFormatter.java
  ===================================================================
  /*****************************************************************************
   * Copyright (C) The Apache Software Foundation. All rights reserved.        *
   * ------------------------------------------------------------------------- *
   * This software is published under the terms of the Apache Software License *
   * version 1.1, a copy of which has been included  with this distribution in *
   * the LICENSE file.                                                         *
   *****************************************************************************/
  
  package org.apache.cocoon.util.log;
  
  import java.io.StringWriter;
  import java.util.Stack;
  import org.apache.log.*;
  
  /**
   * A refactoring of <code>org.apache.log.format.PatternFormatter</code> that
   * can be extended.
   * This formater formats the LogEntries according to a input pattern
   * string.
   *
   * The format of each pattern element can be %[+|-]#.#{field:subformat}
   *
   * The +|- indicates left or right justify.
   * The #.# indicates the minimum and maximum size of output.
   * 'field' indicates which field is to be output and must be one of
   *  proeprties of LogEvent
   * 'subformat' indicates a particular subformat and is currently unused.
   *
   * @author <a href="mailto:[EMAIL PROTECTED]";>Peter Donald</a>
   * @author <a href="[EMAIL PROTECTED]">Sylvain Wallez</a>
   */
  public class ExtensiblePatternFormatter
      implements Formatter
  {
      protected final static int         TYPE_TEXT            = 1;
      protected final static int         TYPE_CATEGORY        = 2;
      protected final static int         TYPE_CONTEXT         = 3;
      protected final static int         TYPE_MESSAGE         = 4;
      protected final static int         TYPE_TIME            = 5;
      protected final static int         TYPE_RELATIVE_TIME   = 6;
      protected final static int         TYPE_THROWABLE       = 7;
      protected final static int         TYPE_PRIORITY        = 8;
      
      /**
       * The maximum value used for TYPEs. Subclasses can define their own TYPEs
       * starting at <code>MAX_TYPE + 1</code>.
       */
      protected final static int         MAX_TYPE             = 8;
       
  
      protected final static String      TYPE_CATEGORY_STR      = "category";
      protected final static String      TYPE_CONTEXT_STR       = "context";
      protected final static String      TYPE_MESSAGE_STR       = "message";
      protected final static String      TYPE_TIME_STR          = "time";
      protected final static String      TYPE_RELATIVE_TIME_STR = "rtime";
      protected final static String      TYPE_THROWABLE_STR     = "throwable";
      protected final static String      TYPE_PRIORITY_STR      = "priority";
  
      protected final static String      SPACE_16               = "                ";
      protected final static String      SPACE_8                = "        ";
      protected final static String      SPACE_4                = "    ";
      protected final static String      SPACE_2                = "  ";
      protected final static String      SPACE_1                = " ";
  
      protected final static String      EOL                    = 
System.getProperty("line.separator", "\n");
  
      protected static class PatternRun
      {
          public String    m_data;
          public boolean   m_rightJustify;
          public int       m_minSize;
          public int       m_maxSize;
          public int       m_type;
          public String    m_format;
      }
  
      protected PatternRun                      m_formatSpecification[];
  
      /**
       * Extract and build a pattern from input string.
       *
       * @param stack the stack on which to place patterns
       * @param pattern the input string
       * @param index the start of pattern run
       * @return the number of characters in pattern run
       */
      protected int addPatternRun( final Stack stack,
                                   final char pattern[],
                                   int index )
      {
          final PatternRun run = new PatternRun();
          final int start = index++;
  
          //first check for a +|- sign
          if( '+' == pattern[ index ] ) index++;
          else if( '-' == pattern[ index ] )
          {
              run.m_rightJustify = true;
              index++;
          }
  
          if( Character.isDigit( pattern[ index ] ))
          {
              int total = 0;
              while( Character.isDigit( pattern[ index ] ) )
              {
                  total = total * 10 + (pattern[ index ] - '0');
                  index++;
              }
              run.m_minSize = total;
          }
  
          //check for . sign indicating a maximum is to follow
          if( index < pattern.length && '.' == pattern[ index ] )
          {
              index++;
  
              if( Character.isDigit( pattern[ index ] ))
              {
                  int total = 0;
                  while( Character.isDigit( pattern[ index ] ) )
                  {
                      total = total * 10 + (pattern[ index ] - '0');
                      index++;
                  }
                  run.m_maxSize = total;
              }
          }
  
          if( index >= pattern.length || '{' != pattern[ index ] )
          {
              throw
                  new IllegalArgumentException( "Badly formed pattern at character " +
                                                index );
          }
  
          int typeStart = index;
  
          while( index < pattern.length &&
                 pattern[ index ]!= ':' && pattern[ index ] != '}' )
          {
              index++;
          }
  
          int typeEnd = index - 1;
  
          final String type =
              new String( pattern, typeStart + 1, typeEnd - typeStart );
  
          run.m_type = getTypeIdFor( type );
  
          if( index < pattern.length && pattern[ index ] == ':' )
          {
              index++;
              while( index < pattern.length && pattern[ index ] != '}' ) index++;
  
              final int length = index - typeEnd - 2;
  
              if( 0 != length )
              {
                  run.m_format = new String( pattern, typeEnd + 2, length );
              }
          }
  
          if( index >= pattern.length || '}' != pattern[ index ] )
          {
              throw new
                  IllegalArgumentException("Unterminated type in pattern at character "
                                           + index );
          }
  
          index++;
  
          stack.push( run );
  
          return index - start;
      }
  
      /**
       * Extract and build a text run  from input string.
       * It does special handling of '\n' and '\t' replaceing
       * them with newline and tab.
       *
       * @param stack the stack on which to place runs
       * @param pattern the input string
       * @param index the start of the text run
       * @return the number of characters in run
       */
      protected int addTextRun( final Stack stack,
                                final char pattern[],
                                int index )
      {
          final PatternRun run = new PatternRun();
          final int start = index;
          boolean escapeMode = false;
  
          if( '%' == pattern[ index ] ) index++;
  
          final StringBuffer sb = new StringBuffer();
  
          while( index < pattern.length && pattern[ index ] != '%' )
          {
              if( escapeMode )
              {
                  if( 'n' == pattern[ index ] ) sb.append( EOL );
                  else if( 't' == pattern[ index ] ) sb.append( '\t' );
                  else sb.append( pattern[ index ] );
                  escapeMode = false;
              }
              else if( '\\' == pattern[ index ] ) escapeMode = true;
              else sb.append( pattern[ index ] );
              index++;
          }
  
          run.m_data = sb.toString();
          run.m_type = TYPE_TEXT;
  
          stack.push( run );
  
          return index - start;
      }
  
      /**
       * Utility to append a string to buffer given certain constraints.
       *
       * @param sb the StringBuffer
       * @param minSize the minimum size of output (0 to ignore)
       * @param maxSize the maximum size of output (0 to ignore)
       * @param rightJustify true if the string is to be right justified in it's box.
       * @param output the input string
       */
      protected void append( final StringBuffer sb,
                             final int minSize,
                             final int maxSize,
                             final boolean rightJustify,
                             final String output )
      {
          final int size = output.length();
  
          if( size < minSize )
          {
              //assert( minSize > 0 );
              if( rightJustify )
              {
                  appendWhiteSpace( sb, minSize - size );
                  sb.append( output );
              }
              else
              {
                  sb.append( output );
                  appendWhiteSpace( sb, minSize - size );
              }
          }
          else if( maxSize > 0 && maxSize < size )
          {
              sb.append( output.substring( 0, maxSize ) );
          }
          else
          {
              sb.append( output );
          }
      }
  
      /**
       * Append a certain number of whitespace characters to a StringBuffer.
       *
       * @param sb the StringBuffer
       * @param length the number of spaces to append
       */
      protected void appendWhiteSpace( final StringBuffer sb, int length )
      {
          while( length >= 16 )
          {
              sb.append( SPACE_16 );
              length -= 16;
          }
  
          if( length >= 8 )
          {
              sb.append( SPACE_8 );
              length -= 8;
          }
  
          if( length >= 4 )
          {
              sb.append( SPACE_4 );
              length -= 4;
          }
  
          if( length >= 2 )
          {
              sb.append( SPACE_2 );
              length -= 2;
          }
  
          if( length >= 1 )
          {
              sb.append( SPACE_1 );
              length -= 1;
          }
      }
  
      /**
       * Format the event according to the pattern.
       *
       * @param event the event
       * @return the formatted output
       */
      public String format( final LogEvent event )
      {
          final StringBuffer sb = new StringBuffer();
  
          String str = null;
  
          for( int i = 0; i < m_formatSpecification.length; i++ )
          {
              final PatternRun run =  m_formatSpecification[ i ];
              
              //treat text differently as it doesn't need min/max padding
              if ( run.m_type == TYPE_TEXT )
              {
                  sb.append( run.m_data );
              }
              else
              {
                  str = formatPatternRun( event, run );
                  
                  if (str != null)
                  {
                      append( sb, run.m_minSize, run.m_maxSize, run.m_rightJustify, 
str );
                  }
              }
          }
  
          return sb.toString();
      }
      
      /**
       * Formats a single pattern run (can be extended in subclasses).
       *
       * @param  run the pattern run to format.
       * @return the formatted result.
       */
      protected String formatPatternRun( final LogEvent event, final PatternRun run )
      {
          String str = null;
  
          switch( run.m_type )
          {
              case TYPE_RELATIVE_TIME:
                  str = getTime( event.getRelativeTime(), run.m_format );
                  break;
  
              case TYPE_TIME:
                  str = getTime( event.getTime(), run.m_format );
                  break;
  
              case TYPE_THROWABLE:
                  str = getStackTrace( event.getThrowable(), run.m_format );
                  break;
  
              case TYPE_MESSAGE:
                  str = getMessage( event.getMessage(), run.m_format );
                  break;
  
              case TYPE_CONTEXT:
                  str = getContext( event.getContextStack(), run.m_format );
                  break;
  
              case TYPE_CATEGORY:
                  str = getCategory( event.getCategory(), run.m_format );
                  break;
  
              case TYPE_PRIORITY:
                  str = getPriority( event.getPriority(), run.m_format );
                  break;
  
              default:
                  //TODO: Convert next line to use error handler
                  Hierarchy.getDefaultHierarchy().log( "Unknown Pattern 
specification." + run.m_type );
          }
          
          return str;
      }
  
      /**
       * Utility method to format category.
       *
       * @param category the category string
       * @param format ancilliary format parameter - allowed to be null
       * @return the formatted string
       */
      protected String getCategory( final String category, final String format )
      {
          return category;
      }
  
      /**
       * Get formatted priority string.
       */
      protected String getPriority( final Priority priority, final String format )
      {
          return priority.getName();
      }
  
      /**
       * Utility method to format context.
       *
       * @param context the context string
       * @param format ancilliary format parameter - allowed to be null
       * @return the formatted string
       */
      protected String getContext( final ContextStack stack, final String format )
      {
          //TODO: Retrieve StringBuffers from a cache
          final StringBuffer sb = new StringBuffer();
          final int size = stack.getSize();
  
          int sizeSpecification = Integer.MAX_VALUE;
  
          if( null != format )
          {
              try { sizeSpecification = Integer.parseInt( format ); }
              catch( final NumberFormatException nfe ) { nfe.printStackTrace(); }
          }
  
          final int end = size - 1;
          final int start = Math.max( end - sizeSpecification + 1, 0 );
  
          for( int i = start; i < end; i++ )
          {
              sb.append( fix( stack.get( i ).toString() ) );
              sb.append( '.' );
          }
  
          sb.append( stack.get( end ) );
  
          return sb.toString();
      }
  
      /**
       * Correct a context string by replacing '.''s with a '_'.
       *
       * @param context the un-fixed context
       * @return the fixed context
       */
      protected final String fix( final String context )
      {
          return context.replace( '.', '_' );
      }
  
      /**
       * Utility method to format message.
       *
       * @param message the message string
       * @param format ancilliary format parameter - allowed to be null
       * @return the formatted string
       */
      protected String getMessage( final String message, final String format )
      {
          return message;
      }
  
      /**
       * Utility method to format stack trace.
       *
       * @param throwable the throwable instance
       * @param format ancilliary format parameter - allowed to be null
       * @return the formatted string
       */
      protected String getStackTrace( final Throwable throwable, final String format )
      {
          if( null == throwable ) return "";
          final StringWriter sw = new StringWriter();
          throwable.printStackTrace( new java.io.PrintWriter( sw ) );
          return sw.toString();
      }
  
      /**
       * Utility method to format time.
       *
       * @param time the time
       * @param format ancilliary format parameter - allowed to be null
       * @return the formatted string
       */
      protected String getTime( final long time, final String format )
      {
          return Long.toString( time );
      }
  
      /**
       * Retrieve the type-id for a particular string.
       *
       * @param type the string
       * @return the type-id
       */
      protected int getTypeIdFor( final String type )
      {
          if( type.equalsIgnoreCase( TYPE_CATEGORY_STR ) ) return TYPE_CATEGORY;
          else if( type.equalsIgnoreCase( TYPE_CONTEXT_STR ) ) return TYPE_CONTEXT;
          else if( type.equalsIgnoreCase( TYPE_MESSAGE_STR ) ) return TYPE_MESSAGE;
          else if( type.equalsIgnoreCase( TYPE_PRIORITY_STR ) ) return TYPE_PRIORITY;
          else if( type.equalsIgnoreCase( TYPE_TIME_STR ) ) return TYPE_TIME;
          else if( type.equalsIgnoreCase( TYPE_RELATIVE_TIME_STR ) ) return 
TYPE_RELATIVE_TIME;
          else if( type.equalsIgnoreCase( TYPE_THROWABLE_STR ) )
          {
              return TYPE_THROWABLE;
          }
          else
          {
              throw new IllegalArgumentException( "Unknown Type in pattern - " +
                                                  type );
          }
      }
  
      /**
       * Parse the input pattern and build internal data structures.
       *
       * @param patternString the pattern
       */
      protected void parse( final String patternString )
      {
          final Stack stack = new Stack();
          final int size = patternString.length();
          final char pattern[] = new char[ size ];
          int index = 0;
  
          patternString.getChars( 0, size, pattern, 0 );
  
          while( index < size )
          {
              if( pattern[ index ] == '%' &&
                  !( index != size - 1 && pattern[ index + 1 ] == '%' ) )
              {
                  index += addPatternRun( stack, pattern, index );
              }
              else
              {
                  index +=  addTextRun( stack, pattern, index );
              }
          }
  
          final int elementCount = stack.size();
  
          m_formatSpecification = new PatternRun[ elementCount ];
  
          for( int i = 0; i < elementCount; i++ )
          {
              m_formatSpecification[ i ] = (PatternRun) stack.elementAt( i );
          }
      }
  
      /**
       * Set the string description that the format is extracted from.
       *
       * @param format the string format
       */
      public void setFormat( final String format )
      {
          parse( format );
      }
  }
  
  
  1.1                  xml-cocoon2/src/org/apache/cocoon/util/log/package.html
  
  Index: package.html
  ===================================================================
  <html>
  <head>
  </head>
  <body>
  LogKit related utilities.
  </body>
  
  

----------------------------------------------------------------------
In case of troubles, e-mail:     [EMAIL PROTECTED]
To unsubscribe, e-mail:          [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to