commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From strub...@apache.org
Subject svn commit: r1423709 - in /commons/sandbox/privilizer/trunk: modules/privilizer/api/src/main/java/org/apache/commons/weaver/ modules/privilizer/api/src/main/java/org/apache/commons/weaver/privilizer/ modules/privilizer/weaver/src/main/java/org/apache/c...
Date Tue, 18 Dec 2012 23:05:38 GMT
Author: struberg
Date: Tue Dec 18 23:05:36 2012
New Revision: 1423709

URL: http://svn.apache.org/viewvc?rev=1423709&view=rev
Log:
committing intermediate stuff for matt to pick up

Added:
    commons/sandbox/privilizer/trunk/modules/privilizer/api/src/main/java/org/apache/commons/weaver/
    commons/sandbox/privilizer/trunk/modules/privilizer/api/src/main/java/org/apache/commons/weaver/privilizer/
    commons/sandbox/privilizer/trunk/modules/privilizer/api/src/main/java/org/apache/commons/weaver/privilizer/Privileged.java   (with props)
    commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/
    commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/
    commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AccessLevel.java   (with props)
    commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AlreadyWovenException.java   (with props)
    commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Body.java   (with props)
    commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/FilesystemPrivilizer.java   (with props)
    commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Privilizer.java   (with props)
    commons/sandbox/privilizer/trunk/processor/src/main/java/org/
    commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/
    commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/
    commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/
    commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/WeaveProcessor.java   (with props)
    commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/spi/
    commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/spi/Weaver.java   (with props)
    commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/utils/
    commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/utils/URLArray.java   (with props)
Removed:
    commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/privilizer/weave/
Modified:
    commons/sandbox/privilizer/trunk/processor/pom.xml

Added: commons/sandbox/privilizer/trunk/modules/privilizer/api/src/main/java/org/apache/commons/weaver/privilizer/Privileged.java
URL: http://svn.apache.org/viewvc/commons/sandbox/privilizer/trunk/modules/privilizer/api/src/main/java/org/apache/commons/weaver/privilizer/Privileged.java?rev=1423709&view=auto
==============================================================================
--- commons/sandbox/privilizer/trunk/modules/privilizer/api/src/main/java/org/apache/commons/weaver/privilizer/Privileged.java (added)
+++ commons/sandbox/privilizer/trunk/modules/privilizer/api/src/main/java/org/apache/commons/weaver/privilizer/Privileged.java Tue Dec 18 23:05:36 2012
@@ -0,0 +1,29 @@
+/*
+ *  Copyright the original author or authors.
+ *
+ *  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.commons.weaver.privilizer;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+/**
+ * Marks a method as needing to execute in a privileged fashion in secured environments.
+ */
+public @interface Privileged {
+}

Propchange: commons/sandbox/privilizer/trunk/modules/privilizer/api/src/main/java/org/apache/commons/weaver/privilizer/Privileged.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AccessLevel.java
URL: http://svn.apache.org/viewvc/commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AccessLevel.java?rev=1423709&view=auto
==============================================================================
--- commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AccessLevel.java (added)
+++ commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AccessLevel.java Tue Dec 18 23:05:36 2012
@@ -0,0 +1,56 @@
+/*
+ *  Copyright the original author or authors.
+ *
+ *  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.commons.weaver.privilizer;
+
+import java.lang.reflect.Modifier;
+import java.util.EnumSet;
+import java.util.Locale;
+
+public enum AccessLevel {
+    PUBLIC(Modifier.PUBLIC), PROTECTED(Modifier.PROTECTED), PACKAGE(0), PRIVATE(Modifier.PRIVATE);
+
+    private final int flag;
+
+    private AccessLevel(int flag) {
+        this.flag = flag;
+    }
+
+    public static AccessLevel of(int mod) {
+        if (Modifier.isPublic(mod)) {
+            return PUBLIC;
+        }
+        if (Modifier.isProtected(mod)) {
+            return PROTECTED;
+        }
+        if (Modifier.isPrivate(mod)) {
+            return PRIVATE;
+        }
+        return PACKAGE;
+    }
+
+    public int merge(int mod) {
+        int remove = 0;
+        for (AccessLevel accessLevel : EnumSet.complementOf(EnumSet.of(this))) {
+            remove |= accessLevel.flag;
+        }
+        return mod & ~remove | flag;
+    }
+
+    @Override
+    public String toString() {
+        return name().toLowerCase(Locale.US);
+    }
+}

Propchange: commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AccessLevel.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AlreadyWovenException.java
URL: http://svn.apache.org/viewvc/commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AlreadyWovenException.java?rev=1423709&view=auto
==============================================================================
--- commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AlreadyWovenException.java (added)
+++ commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AlreadyWovenException.java Tue Dec 18 23:05:36 2012
@@ -0,0 +1,39 @@
+/*
+ *  Copyright the original author or authors.
+ *
+ *  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.commons.weaver.privilizer;
+
+public class AlreadyWovenException extends IllegalStateException {
+    private static final long serialVersionUID = 1L;
+    private static final String MESSAGE = "%s already woven with policy %s";
+
+    private final String classname;
+    private final Privilizer.Policy policy;
+
+    public AlreadyWovenException(String classname, Privilizer.Policy policy) {
+        super(String.format(MESSAGE, classname, policy));
+        this.classname = classname;
+        this.policy = policy;
+    }
+
+    public String getClassname() {
+        return classname;
+    }
+
+    public Privilizer.Policy getPolicy() {
+        return policy;
+    }
+
+}

Propchange: commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/AlreadyWovenException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Body.java
URL: http://svn.apache.org/viewvc/commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Body.java?rev=1423709&view=auto
==============================================================================
--- commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Body.java (added)
+++ commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Body.java Tue Dec 18 23:05:36 2012
@@ -0,0 +1,107 @@
+/*
+ *  Copyright the original author or authors.
+ *
+ *  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.commons.weaver.privilizer;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.text.StrBuilder;
+
+class Body implements CharSequence {
+    private final String indent = "  ";
+    private final StrBuilder content = new StrBuilder();
+    private int level = 0;
+    private boolean lineStarted;
+
+    {
+        startBlock();
+    }
+
+    public char charAt(int index) {
+        return content.charAt(index);
+    }
+
+    public int length() {
+        return content.length();
+    }
+
+    public CharSequence subSequence(int start, int end) {
+        return content.subSequence(start, end);
+    }
+
+    @Override
+    public String toString() {
+        return content.toString();
+    }
+
+    Body append(char c) {
+        content.append(c);
+        return this;
+    }
+
+    Body append(String format, Object... args) {
+        prepare();
+        content.append(String.format(format, args));
+        return this;
+    }
+
+    Body appendLine(String format, Object... args) {
+        return append(format, args).appendNewLine();
+    }
+
+    Body appendNewLine() {
+        content.appendNewLine();
+        lineStarted = false;
+        return this;
+    }
+
+    Body complete() {
+        try {
+            return endBlock();
+        } finally {
+            Validate.validState(level == 0);
+        }
+    }
+
+    Body endBlock() {
+        if (level < 1) {
+            throw new IllegalStateException();
+        }
+        level--;
+        prepare();
+        append('}').appendNewLine();
+        return this;
+    }
+
+    Body startBlock() {
+        if (lineStarted) {
+            append(' ');
+        }
+        level++;
+        return append('{').appendNewLine();
+    }
+
+    Body startBlock(String format, Object... args) {
+        append(format, args);
+        return startBlock();
+    }
+
+    private void prepare() {
+        if (!lineStarted) {
+            lineStarted = true;
+            content.append(StringUtils.repeat(indent, level));
+        }
+    }
+}

Propchange: commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Body.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/FilesystemPrivilizer.java
URL: http://svn.apache.org/viewvc/commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/FilesystemPrivilizer.java?rev=1423709&view=auto
==============================================================================
--- commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/FilesystemPrivilizer.java (added)
+++ commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/FilesystemPrivilizer.java Tue Dec 18 23:05:36 2012
@@ -0,0 +1,165 @@
+/*
+ *  Copyright the original author or authors.
+ *
+ *  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.commons.weaver.privilizer;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.LoaderClassPath;
+import javassist.NotFoundException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.RegexFileFilter;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.xbean.finder.AnnotationFinder;
+import org.apache.xbean.finder.archive.FileArchive;
+
+
+/**
+ * Handles weaving of methods annotated with {@link Privileged}.
+ */
+public class FilesystemPrivilizer extends Privilizer<FilesystemPrivilizer> {
+    private static ClassPool createClassPool(ClassLoader classpath, File target) {
+        final ClassPool result = new ClassPool();
+        try {
+            result.appendClassPath(validTarget(target).getAbsolutePath());
+            result.appendClassPath(new LoaderClassPath(classpath));
+            result.appendPathList(System.getProperty("java.class.path"));
+        } catch (final NotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        return result;
+    }
+
+    private static Set<Class<?>> getDeclaringClasses(Iterable<Method> methods) {
+        final Set<Class<?>> declaringClasses = new HashSet<Class<?>>();
+        for (final Method method : methods) {
+            declaringClasses.add(method.getDeclaringClass());
+        }
+        return declaringClasses;
+    }
+
+    private static Class<?> getOutermost(Class<?> type) {
+        Class<?> enclosing = type.getEnclosingClass();
+        return enclosing == null ? type : getOutermost(enclosing);
+    }
+
+    private static File validTarget(File target) {
+        Validate.notNull(target, "target");
+        Validate.isTrue(target.isDirectory(), "not a directory");
+        return target;
+    }
+
+    private final ClassLoader classpath;
+
+    private final File target;
+
+    private final ClassFileWriter classFileWriter = new ClassFileWriter() {
+        @Override
+        public void write(CtClass type) throws CannotCompileException, IOException {
+            type.writeFile(target.getAbsolutePath());
+        }
+    };
+
+    public FilesystemPrivilizer(ClassLoader classpath, File target) {
+        super(createClassPool(classpath, target));
+        this.classpath = classpath;
+        this.target = target;
+    }
+
+    public FilesystemPrivilizer(Policy policy, ClassLoader classpath, File target) {
+        super(policy, createClassPool(classpath, target));
+        this.classpath = classpath;
+        this.target = target;
+    }
+
+    /**
+     * Clear the way by deleting classfiles woven with a different
+     * {@link Policy}.
+     * 
+     * @throws NotFoundException
+     */
+    public void prepare() throws NotFoundException {
+        info("preparing %s; policy = %s", target, policy);
+        final Set<File> toDelete = new TreeSet<File>();
+        for (final Class<?> type : getDeclaringClasses(findPrivilegedMethods())) {
+            final CtClass ctClass = classPool.get(type.getName());
+            final String policyValue = toString(ctClass.getAttribute(generateName(POLICY_NAME)));
+            if (policyValue == null || policyValue.equals(policy.name())) {
+                continue;
+            }
+            debug("class %s previously woven with policy %s", type.getName(), policyValue);
+            final File packageDir =
+                new File(target, StringUtils.replaceChars(ctClass.getPackageName(), '.', File.separatorChar));
+
+            // simple classname of outermost class, plus any inner classes:
+            final String pattern =
+                new StringBuilder(getOutermost(type).getSimpleName()).append("(\\$.+)??\\.class").toString();
+
+            debug("searching %s for pattern '%s'", packageDir.getAbsolutePath(), pattern);
+            toDelete.addAll(FileUtils.listFiles(packageDir, new RegexFileFilter(pattern), null));
+        }
+        if (toDelete.isEmpty()) {
+            return;
+        }
+        info("Deleting %s files...", toDelete.size());
+        debug(toDelete.toString());
+        for (File f : toDelete) {
+            if (!f.delete()) {
+                debug("Failed to delete %s", f);
+            }
+        }
+    }
+
+    /**
+     * Weave all {@link Privileged} methods found.
+     * 
+     * @throws NotFoundException
+     * @throws IOException
+     * @throws CannotCompileException
+     * @throws ClassNotFoundException
+     */
+    public void weaveAll() throws NotFoundException, IOException, CannotCompileException, ClassNotFoundException {
+        int woven = 0;
+        for (final Class<?> type : getDeclaringClasses(findPrivilegedMethods())) {
+            if (weave(classPool.get(type.getName()))) {
+                woven++;
+            }
+        }
+        if (woven > 0) {
+            info("Wove %s classes.", woven);
+        }
+    }
+
+    @Override
+    protected ClassFileWriter getClassFileWriter() {
+        return classFileWriter;
+    }
+
+    private List<Method> findPrivilegedMethods() {
+        final AnnotationFinder annotationFinder = new AnnotationFinder(new FileArchive(classpath, target), false);
+        return annotationFinder.findAnnotatedMethods(Privileged.class);
+    }
+}

Propchange: commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/FilesystemPrivilizer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Privilizer.java
URL: http://svn.apache.org/viewvc/commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Privilizer.java?rev=1423709&view=auto
==============================================================================
--- commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Privilizer.java (added)
+++ commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Privilizer.java Tue Dec 18 23:05:36 2012
@@ -0,0 +1,489 @@
+/*
+ *  Copyright the original author or authors.
+ *
+ *  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.commons.weaver.privilizer;
+
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.nio.charset.Charset;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Logger;
+
+import javassist.CannotCompileException;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.CtField;
+import javassist.CtMethod;
+import javassist.CtNewConstructor;
+import javassist.CtNewMethod;
+import javassist.CtPrimitiveType;
+import javassist.NotFoundException;
+import javassist.bytecode.Descriptor;
+
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.text.StrBuilder;
+
+
+/**
+ * Handles weaving of methods annotated with {@link Privileged}.
+ */
+public abstract class Privilizer<SELF extends Privilizer<SELF>> {
+    public interface ClassFileWriter {
+        void write(CtClass type) throws CannotCompileException, IOException;
+    }
+
+    public interface Log {
+        void debug(String message);
+
+        void verbose(String message);
+
+        void error(String message);
+
+        void info(String message);
+
+        void warn(String message);
+    }
+
+    /**
+     * Weaving policy: when to use {@link PrivilegedAction}s.
+     */
+    public enum Policy {
+        /**
+         * Disables weaving.
+         */
+        NEVER,
+
+        /**
+         * Weaves such that the check for an active {@link SecurityManager} is
+         * done once only.
+         */
+        ON_INIT(generateName("hasSecurityManager")),
+
+        /**
+         * Weaves such that the check for an active {@link SecurityManager} is
+         * done for each {@link Privileged} method execution.
+         */
+        DYNAMIC(HAS_SECURITY_MANAGER_CONDITION),
+
+        /**
+         * Weaves such that {@link Privileged} methods are always executed as
+         * such.
+         */
+        ALWAYS;
+
+        private final String condition;
+
+        private Policy() {
+            this(null);
+        }
+
+        private Policy(String condition) {
+            this.condition = condition;
+        }
+
+        private boolean isConditional() {
+            return condition != null;
+        }
+    }
+
+    protected static final String POLICY_NAME = "policyName";
+
+    private static final String ACTION_SUFFIX = "_ACTION";
+
+    private static final String GENERATE_NAME = "__privileged_%s";
+    private static final String HAS_SECURITY_MANAGER_CONDITION = "System.getSecurityManager() != null";
+
+    protected static String generateName(String simple) {
+        return String.format(GENERATE_NAME, simple);
+    }
+
+    protected static String toString(byte[] b) {
+        return b == null ? null : new String(b, Charset.forName("UTF-8"));
+    }
+
+    protected final Policy policy;
+
+    protected final ClassPool classPool;
+
+    private boolean settingsReported;
+
+    private Log log = new Log() {
+        final Logger logger = Logger.getLogger(Privilizer.class.getName());
+
+        @Override
+        public void debug(String message) {
+            logger.finer(message);
+        }
+
+        @Override
+        public void verbose(String message) {
+            logger.fine(message);
+        }
+
+        @Override
+        public void error(String message) {
+            logger.severe(message);
+        }
+
+        @Override
+        public void info(String message) {
+            logger.info(message);
+        }
+
+        @Override
+        public void warn(String message) {
+            logger.warning(message);
+        }
+
+    };
+
+    private static final Comparator<CtMethod> CTMETHOD_COMPARATOR = new Comparator<CtMethod>() {
+
+        @Override
+        public int compare(CtMethod arg0, CtMethod arg1) {
+            if (ObjectUtils.equals(arg0, arg1)) {
+                return 0;
+            }
+            if (arg0 == null) {
+                return -1;
+            }
+            if (arg1 == null) {
+                return 1;
+            }
+            final int result = ObjectUtils.compare(arg0.getName(), arg1.getName());
+            return result == 0 ? ObjectUtils.compare(arg0.getSignature(), arg1.getSignature()) : result;
+        }
+    };
+
+    private static Set<CtMethod> getPrivilegedMethods(CtClass type) throws ClassNotFoundException {
+        final TreeSet<CtMethod> result = new TreeSet<CtMethod>(CTMETHOD_COMPARATOR);
+        for (final CtMethod m : type.getDeclaredMethods()) {
+            if (Modifier.isAbstract(m.getModifiers()) || m.getAnnotation(Privileged.class) == null) {
+                continue;
+            }
+            result.add(m);
+        }
+        return result;
+    }
+
+    public Privilizer(ClassPool classPool) {
+        this(Policy.DYNAMIC, classPool);
+    }
+
+    public Privilizer(Policy policy, ClassPool classPool) {
+        this.policy = Validate.notNull(policy, "policy");
+        this.classPool = Validate.notNull(classPool, "classPool");
+    }
+
+    public SELF loggingTo(Log log) {
+        this.log = Validate.notNull(log);
+        settingsReported = false;
+        @SuppressWarnings("unchecked")
+        final SELF self = (SELF) this;
+        return self;
+    }
+
+    /**
+     * Weave the specified class.
+     * 
+     * @param type
+     * @return whether any work was done
+     * @throws NotFoundException
+     * @throws IOException
+     * @throws CannotCompileException
+     * @throws ClassNotFoundException
+     */
+    public boolean weave(CtClass type) throws NotFoundException, IOException, CannotCompileException,
+        ClassNotFoundException {
+        reportSettings();
+        final String policyName = generateName(POLICY_NAME);
+        final String policyValue = toString(type.getAttribute(policyName));
+        if (policyValue != null) {
+            verbose("%s already woven with policy %s", type.getName(), policyValue);
+            if (!policy.name().equals(policyValue)) {
+                throw new AlreadyWovenException(type.getName(), Policy.valueOf(policyValue));
+            }
+            return false;
+        }
+        boolean result = false;
+        if (policy.compareTo(Policy.NEVER) > 0) {
+            if (policy == Policy.ON_INIT) {
+                debug("Initializing field %s to %s", policy.condition, HAS_SECURITY_MANAGER_CONDITION);
+                type.addField(new CtField(CtClass.booleanType, policy.condition, type),
+                    CtField.Initializer.byExpr(HAS_SECURITY_MANAGER_CONDITION));
+            }
+            for (final CtMethod m : getPrivilegedMethods(type)) {
+                result |= weave(type, m);
+            }
+            if (result) {
+                type.setAttribute(policyName, policy.name().getBytes(Charset.forName("UTF-8")));
+                getClassFileWriter().write(type);
+            }
+        }
+        log.verbose(String.format(result ? "Wove class %s" : "Nothing to do for class %s", type.getName()));
+        return result;
+    }
+
+    protected void debug(String message, Object... args) {
+        log.debug(String.format(message, args));
+    }
+
+    protected void verbose(String message, Object... args) {
+        log.verbose(String.format(message, args));
+    }
+
+    protected void warn(String message, Object... args) {
+        log.warn(String.format(message, args));
+    }
+
+    protected abstract ClassFileWriter getClassFileWriter();
+
+    protected void info(String message, Object... args) {
+        log.info(String.format(message, args));
+    }
+
+    protected boolean permitMethodWeaving(AccessLevel accessLevel) {
+        return true;
+    }
+
+    private CtClass createAction(CtClass type, CtMethod impl, Class<?> iface) throws NotFoundException,
+        CannotCompileException, IOException {
+        final boolean exc = impl.getExceptionTypes().length > 0;
+
+        final CtClass actionType = classPool.get(iface.getName());
+
+        final String simpleName = generateActionClassname(impl);
+        debug("Creating action type %s for method %s", simpleName, toString(impl));
+        final CtClass result = type.makeNestedClass(simpleName, true);
+        result.addInterface(actionType);
+
+        final CtField owner;
+        if (Modifier.isStatic(impl.getModifiers())) {
+            owner = null;
+        } else {
+            owner = new CtField(type, generateName("owner"), result);
+            owner.setModifiers(Modifier.PRIVATE | Modifier.FINAL);
+            debug("Adding owner field %s to %s", owner.getName(), simpleName);
+            result.addField(owner);
+        }
+
+        final List<String> propagatedParameters = new ArrayList<String>();
+        int index = -1;
+        for (final CtClass param : impl.getParameterTypes()) {
+            final String f = String.format("arg%s", Integer.valueOf(++index));
+            final CtField fld = new CtField(param, f, result);
+            fld.setModifiers(Modifier.PRIVATE | Modifier.FINAL);
+            debug("Copying parameter %s from %s to %s.%s", index, toString(impl), simpleName, f);
+            result.addField(fld);
+            propagatedParameters.add(f);
+        }
+        {
+            final StrBuilder constructor = new StrBuilder(simpleName).append('(');
+            boolean sep = false;
+            final Body body = new Body();
+
+            for (final CtField fld : result.getDeclaredFields()) {
+                if (sep) {
+                    constructor.append(", ");
+                } else {
+                    sep = true;
+                }
+                constructor.append(fld.getType().getName()).append(' ').append(fld.getName());
+                body.appendLine("this.%1$s = %1$s;", fld.getName());
+            }
+            constructor.append(") ").append(body.complete());
+
+            final String c = constructor.toString();
+            debug("Creating action constructor:");
+            debug(c);
+            result.addConstructor(CtNewConstructor.make(c, result));
+        }
+        {
+            final StrBuilder run = new StrBuilder("public Object run() ");
+            if (exc) {
+                run.append("throws Exception ");
+            }
+            final Body body = new Body();
+            final CtClass rt = impl.getReturnType();
+            final boolean isVoid = rt.equals(CtClass.voidType);
+            if (!isVoid) {
+                body.append("return ");
+            }
+            final String deref = Modifier.isStatic(impl.getModifiers()) ? type.getName() : owner.getName();
+            final String call =
+                String.format("%s.%s(%s)", deref, impl.getName(), StringUtils.join(propagatedParameters, ", "));
+
+            if (!isVoid && rt.isPrimitive()) {
+                body.appendLine("%2$s.valueOf(%1$s);", call, ((CtPrimitiveType) rt).getWrapperName());
+            } else {
+                body.append(call).append(';').appendNewLine();
+
+                if (isVoid) {
+                    body.appendLine("return null;");
+                }
+            }
+
+            run.append(body.complete());
+
+            final String r = run.toString();
+            debug("Creating run method:");
+            debug(r);
+            result.addMethod(CtNewMethod.make(r, result));
+        }
+        getClassFileWriter().write(result);
+        debug("Returning action type %s", result);
+        return result;
+    }
+
+    private String generateActionClassname(CtMethod m) throws NotFoundException {
+        final StringBuilder b = new StringBuilder(m.getName());
+        if (m.getParameterTypes().length > 0) {
+            b.append("$$").append(
+                StringUtils.strip(Descriptor.getParamDescriptor(m.getSignature()), "(;)").replace("[", "ARRAYOF_")
+                    .replace('/', '_').replace(';', '$'));
+        }
+        return b.append(ACTION_SUFFIX).toString();
+    }
+
+    private String toString(CtMethod m) {
+        return String.format("%s%s", m.getName(), m.getSignature());
+    }
+
+    private boolean weave(CtClass type, CtMethod method) throws ClassNotFoundException, CannotCompileException,
+        NotFoundException, IOException {
+        final AccessLevel accessLevel = AccessLevel.of(method.getModifiers());
+        if (!permitMethodWeaving(accessLevel)) {
+            warn("Ignoring %s method %s.%s", accessLevel, type.getName(), toString(method));
+            return false;
+        }
+        if (AccessLevel.PACKAGE.compareTo(accessLevel) > 0) {
+            warn("Possible security leak: granting privileges to %s method %s.%s", accessLevel, type.getName(),
+                toString(method));
+        }
+        final String implName = generateName(method.getName());
+
+        final CtMethod impl = CtNewMethod.copy(method, implName, type, null);
+        impl.setModifiers(AccessLevel.PRIVATE.merge(method.getModifiers()));
+        type.addMethod(impl);
+        debug("Copied %2$s %1$s.%3$s to %4$s %1$s.%5$s", type.getName(), accessLevel, toString(method),
+            AccessLevel.PRIVATE, toString(impl));
+
+        final Body body = new Body();
+        if (policy.isConditional()) {
+            body.startBlock("if (%s)", policy.condition);
+        }
+
+        final boolean exc = method.getExceptionTypes().length > 0;
+
+        if (exc) {
+            body.startBlock("try");
+        }
+
+        final Class<?> iface = exc ? PrivilegedExceptionAction.class : PrivilegedAction.class;
+        final CtClass actionType = createAction(type, impl, iface);
+        final String action = generateName("action");
+
+        body.append("final %s %s = new %s(", iface.getName(), action, actionType.getName());
+        boolean firstParam;
+        if (Modifier.isStatic(impl.getModifiers())) {
+            firstParam = true;
+        } else {
+            body.append("$0");
+            firstParam = false;
+        }
+        for (int i = 1, sz = impl.getParameterTypes().length; i <= sz; i++) {
+            if (firstParam) {
+                firstParam = false;
+            } else {
+                body.append(", ");
+            }
+            body.append('$').append(Integer.toString(i));
+        }
+        body.appendLine(");");
+
+        final CtClass rt = method.getReturnType();
+        final boolean isVoid = rt.equals(CtClass.voidType);
+
+        final String doPrivileged = String.format("%1$s.doPrivileged(%2$s)", AccessController.class.getName(), action);
+        if (isVoid) {
+            body.append(doPrivileged).append(';').appendNewLine();
+            if (policy.isConditional()) {
+                body.appendLine("return;");
+            }
+        } else {
+            final String cast = rt.isPrimitive() ? ((CtPrimitiveType) rt).getWrapperName() : rt.getName();
+            // don't worry about wrapper NPEs because we should be simply
+            // passing back an autoboxed value, then unboxing again
+            final String result = generateName("result");
+            body.appendLine("final %1$s %3$s = (%1$s) %2$s;", cast, doPrivileged, result);
+            body.append("return %s", result);
+            if (rt.isPrimitive()) {
+                body.append(".%sValue()", rt.getName());
+            }
+            body.append(';').appendNewLine();
+        }
+
+        if (exc) {
+            body.endBlock();
+            final String e = generateName("e");
+            body.startBlock("catch (%1$s %2$s)", PrivilegedActionException.class.getName(), e).appendNewLine();
+
+            final String wrapped = generateName("wrapped");
+
+            body.appendLine("final Exception %1$s = %2$s.getCause();", wrapped, e);
+            for (final CtClass thrown : method.getExceptionTypes()) {
+                body.startBlock("if (%1$s instanceof %2$s)", wrapped, thrown.getName());
+                body.appendLine("throw (%2$s) %1$s;", wrapped, thrown.getName());
+                body.endBlock();
+            }
+            body.appendLine(
+                "throw %1$s instanceof RuntimeException ? (RuntimeException) %1$s : new RuntimeException(%1$s);",
+                wrapped);
+            body.endBlock();
+        }
+
+        if (policy.isConditional()) {
+            // close if block we opened before:
+            body.endBlock();
+            // no security manager=> just call impl:
+            if (!isVoid) {
+                body.append("return ");
+            }
+            body.appendLine("%s($$);", impl.getName());
+        }
+
+        final String block = body.complete().toString();
+        debug("Setting body of %s to:\n%s", toString(method), block);
+        method.setBody(block);
+        return true;
+    }
+
+    private void reportSettings() {
+        if (!settingsReported) {
+            settingsReported = true;
+            info("Weave policy == %s", policy);
+        }
+    }
+}

Propchange: commons/sandbox/privilizer/trunk/modules/privilizer/weaver/src/main/java/org/apache/commons/weaver/privilizer/Privilizer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/sandbox/privilizer/trunk/processor/pom.xml
URL: http://svn.apache.org/viewvc/commons/sandbox/privilizer/trunk/processor/pom.xml?rev=1423709&r1=1423708&r2=1423709&view=diff
==============================================================================
--- commons/sandbox/privilizer/trunk/processor/pom.xml (original)
+++ commons/sandbox/privilizer/trunk/processor/pom.xml Tue Dec 18 23:05:36 2012
@@ -32,4 +32,13 @@
     enhancing.
   </description>
 
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.xbean</groupId>
+      <artifactId>xbean-finder</artifactId>
+      <version>3.12</version>
+    </dependency>
+
+  </dependencies>
+
 </project>

Added: commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/WeaveProcessor.java
URL: http://svn.apache.org/viewvc/commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/WeaveProcessor.java?rev=1423709&view=auto
==============================================================================
--- commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/WeaveProcessor.java (added)
+++ commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/WeaveProcessor.java Tue Dec 18 23:05:36 2012
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+package org.apache.commons.weaver;
+
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.ServiceLoader;
+
+import org.apache.commons.weaver.spi.Weaver;
+import org.apache.commons.weaver.utils.URLArray;
+import org.apache.xbean.finder.AnnotationFinder;
+import org.apache.xbean.finder.archive.FileArchive;
+
+/**
+ * This class picks up all Weaver plugins and process them in one go.
+ */
+public class WeaveProcessor {
+
+    private static WeaveProcessor instance;
+
+    /** List of picked up weaver plugins */
+    private List<Weaver> weavers = new ArrayList<Weaver>();
+
+    /** List of classpath entries to perform weaving on*/
+    private List<File> classPathsToWeave = new ArrayList<File>();
+
+    public static synchronized WeaveProcessor getInstance() {
+        if (instance == null) {
+            instance = new WeaveProcessor();
+            instance.init();
+        }
+
+        return instance;
+    }
+
+    private void init() {
+        ServiceLoader<Weaver> loader = ServiceLoader.load(Weaver.class);
+        Iterator<Weaver> it = loader.iterator();
+
+        while (it.hasNext()) {
+            weavers.add(it.next());
+        }
+    }
+
+    /**
+     * All the class paths which should get weaved.
+     * This e.g. contains target/classes in a typical maven installation.
+     */
+    public void addClassPath(File classPath) {
+        classPathsToWeave.add(classPath);
+    }
+
+    /**
+     * configure all Weavers.
+     */
+    public void configure(Properties config) {
+        for (Weaver weaver : weavers) {
+            weaver.configure(config);
+        }
+    }
+
+    /**
+     * perform the weaving on all specified classpath entries
+     */
+    public void weave() {
+        for (Weaver weaver : weavers) {
+            weaver.preWeave();
+        }
+
+        for (Weaver weaver : weavers) {
+            weave(weaver);
+        }
+
+        for (Weaver weaver : weavers) {
+            weaver.postWeave();
+        }
+    }
+
+    private void weave(Weaver weaver) {
+        List<Class<? extends Annotation>> interest = weaver.getInterest();
+
+        ClassLoader classLoader = new URLClassLoader(URLArray.fromFiles(classPathsToWeave));
+
+        AnnotationFinder annotationFinder = new AnnotationFinder(new FileArchive(classLoader, target), false);
+        for (Class<? extends Annotation> annotation : interest) {
+            List<Class<?>> annotatedClasses = annotationFinder.findAnnotatedClasses(annotation);
+
+            for (Class<?> annotatedClass : annotatedClasses) {
+                weaver.weave(annotatedClass);
+            }
+
+
+            List<Method> annotateMethods = annotationFinder.findAnnotatedMethods(annotation);
+            for (Method annotatedMethod : annotateMethods) {
+                weaver.weave(annotatedMethod);
+            }
+        }
+    }
+}

Propchange: commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/WeaveProcessor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/spi/Weaver.java
URL: http://svn.apache.org/viewvc/commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/spi/Weaver.java?rev=1423709&view=auto
==============================================================================
--- commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/spi/Weaver.java (added)
+++ commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/spi/Weaver.java Tue Dec 18 23:05:36 2012
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+package org.apache.commons.weaver.spi;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * An implementation of a 'Weaver' takes care about
+ * certain weaving jobs and will perform the byte code
+ * enhancement in the classes.
+ *
+ * TODO: we might enhance this SPI to gather upfront information about what needs to get scanned at all!
+ */
+public interface Weaver
+{
+    /**
+     * This is for now a simple way to configure any weaver.
+     * Any configuration property of a weaver should start with it's 'name'
+     * e.g. 'privilizer'
+     */
+    void configure(Properties config);
+
+    /**
+     * A Weaver must return a List of Annotations he is interested in.
+     */
+    List<Class<? extends Annotation>> getInterest();
+
+    /**
+     * This will get invoked before any weaver did run
+     */
+    void preWeave();
+
+    /**
+     * Perform weaving on the given class for any class which has one of the required annotations.
+     * If there is nothing to do, then just go on.
+     *
+     * @return <code>true</code> if some bytecode has been changed
+     */
+    boolean weave(Class classToWeave);
+
+    /**
+     * Perform weaving on the given class for any class which has one of the required annotations.
+     * If there is nothing to do, then just go on.
+     *
+     * @return <code>true</code> if some bytecode has been changed
+     */
+    boolean weave(Method methodToWeave);
+
+
+    /**
+     * This method will get invoked after all {@link #weave(Class)} methods got invoked
+     * for all classes on every weaver.
+     */
+    void postWeave();
+}

Propchange: commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/spi/Weaver.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/utils/URLArray.java
URL: http://svn.apache.org/viewvc/commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/utils/URLArray.java?rev=1423709&view=auto
==============================================================================
--- commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/utils/URLArray.java (added)
+++ commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/utils/URLArray.java Tue Dec 18 23:05:36 2012
@@ -0,0 +1,83 @@
+/*
+ *  Copyright the original author or authors.
+ *
+ *  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.commons.weaver.utils;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * {@link URL} Array utilities.
+ */
+public abstract class URLArray {
+    private URLArray() {
+    }
+
+    /**
+     * Convert an {@link Iterable} of filesystem paths.
+     * 
+     * @param files
+     * @return URL[]
+     */
+    public static URL[] fromPaths(final Iterable<String> files) {
+        return fromFiles(new Iterable<File>() {
+
+            public Iterator<File> iterator() {
+                final Iterator<String> path = files.iterator();
+                return new Iterator<File>() {
+
+                    public boolean hasNext() {
+                        return path.hasNext();
+                    }
+
+                    public File next() {
+                        final String p = path.next();
+                        return p == null ? null : new File(p);
+                    }
+
+                    public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+        });
+    }
+
+    /**
+     * Convert an {@link Iterable} of {@link File}s.
+     * 
+     * @param files
+     * @return URL[]
+     */
+    public static URL[] fromFiles(Iterable<File> files) {
+        final ArrayList<URL> result = new ArrayList<URL>();
+        for (File f : files) {
+            if (f == null) {
+                result.add(null);
+                continue;
+            }
+            try {
+                result.add(f.toURI().toURL());
+            } catch (MalformedURLException e) {
+                // this shouldn't happen
+                throw new RuntimeException(e);
+            }
+        }
+        return result.toArray(new URL[result.size()]);
+    }
+}

Propchange: commons/sandbox/privilizer/trunk/processor/src/main/java/org/apache/commons/weaver/utils/URLArray.java
------------------------------------------------------------------------------
    svn:eol-style = native



Mime
View raw message