Return-Path: X-Original-To: apmail-geronimo-scm-archive@www.apache.org Delivered-To: apmail-geronimo-scm-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id CB36111126 for ; Tue, 26 Aug 2014 18:18:26 +0000 (UTC) Received: (qmail 98777 invoked by uid 500); 26 Aug 2014 18:18:26 -0000 Delivered-To: apmail-geronimo-scm-archive@geronimo.apache.org Received: (qmail 98728 invoked by uid 500); 26 Aug 2014 18:18:26 -0000 Mailing-List: contact scm-help@geronimo.apache.org; run by ezmlm Precedence: bulk list-help: list-unsubscribe: List-Post: Reply-To: dev@geronimo.apache.org List-Id: Delivered-To: mailing list scm@geronimo.apache.org Received: (qmail 98710 invoked by uid 99); 26 Aug 2014 18:18:26 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 26 Aug 2014 18:18:26 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 26 Aug 2014 18:17:45 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 29FFD2388C8A; Tue, 26 Aug 2014 18:17:18 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1620683 [12/17] - in /geronimo/specs/trunk: ./ geronimo-javamail_1.5_spec/ geronimo-javamail_1.5_spec/src/ geronimo-javamail_1.5_spec/src/main/ geronimo-javamail_1.5_spec/src/main/java/ geronimo-javamail_1.5_spec/src/main/java/javax/ geron... Date: Tue, 26 Aug 2014 18:17:09 -0000 To: scm@geronimo.apache.org From: rmannibucau@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20140826181718.29FFD2388C8A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/util/SharedFileInputStream.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/util/SharedFileInputStream.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/util/SharedFileInputStream.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/javax/mail/util/SharedFileInputStream.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,596 @@ +/* + * 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 javax.mail.util; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; + +import javax.mail.internet.SharedInputStream; + +public class SharedFileInputStream extends BufferedInputStream implements SharedInputStream { + + + // This initial size isn't documented, but bufsize is 2048 after initialization for the + // Sun implementation. + private static final int DEFAULT_BUFFER_SIZE = 2048; + + // the shared file information, used to synchronize opens/closes of the base file. + private SharedFileSource source; + + /** + * The file offset that is the first byte in the read buffer. + */ + protected long bufpos; + + /** + * The normal size of the read buffer. + */ + protected int bufsize; + + /** + * The size of the file subset represented by this stream instance. + */ + protected long datalen; + + /** + * The source of the file data. This is shared across multiple + * instances. + */ + protected RandomAccessFile in; + + /** + * The starting position of data represented by this stream relative + * to the start of the file data. This stream instance represents + * data in the range start to (start + datalen - 1). + */ + protected long start; + + + /** + * Construct a SharedFileInputStream from a file name, using the default buffer size. + * + * @param file The name of the file. + * + * @exception IOException + */ + public SharedFileInputStream(final String file) throws IOException { + this(file, DEFAULT_BUFFER_SIZE); + } + + + /** + * Construct a SharedFileInputStream from a File object, using the default buffer size. + * + * @param file The name of the file. + * + * @exception IOException + */ + public SharedFileInputStream(final File file) throws IOException { + this(file, DEFAULT_BUFFER_SIZE); + } + + + /** + * Construct a SharedFileInputStream from a file name, with a given initial buffer size. + * + * @param file The name of the file. + * @param bufferSize The initial buffer size. + * + * @exception IOException + */ + public SharedFileInputStream(final String file, final int bufferSize) throws IOException { + // I'm not sure this is correct or not. The SharedFileInputStream spec requires this + // be a subclass of BufferedInputStream. The BufferedInputStream constructor takes a stream, + // which we're not really working from at this point. Using null seems to work so far. + super(null); + init(new File(file), bufferSize); + } + + + /** + * Construct a SharedFileInputStream from a File object, with a given initial buffer size. + * + * @param file The name of the file. + * @param bufferSize The initial buffer size. + * + * @exception IOException + */ + public SharedFileInputStream(final File file, final int bufferSize) throws IOException { + // I'm not sure this is correct or not. The SharedFileInputStream spec requires this + // be a subclass of BufferedInputStream. The BufferedInputStream constructor takes a stream, + // which we're not really working from at this point. Using null seems to work so far. + super(null); + init(file, bufferSize); + } + + + /** + * Private constructor used to spawn off a shared instance + * of this stream. + * + * @param source The internal class object that manages the shared resources of + * the stream. + * @param start The starting offset relative to the beginning of the file. + * @param len The length of file data in this shared instance. + * @param bufsize The initial buffer size (same as the spawning parent. + */ + private SharedFileInputStream(final SharedFileSource source, final long start, final long len, final int bufsize) { + super(null); + this.source = source; + in = source.open(); + this.start = start; + bufpos = start; + datalen = len; + this.bufsize = bufsize; + buf = new byte[bufsize]; + // other fields such as pos and count initialized by the super class constructor. + } + + + /** + * Shared initializtion routine for the constructors. + * + * @param file The file we're accessing. + * @param bufferSize The initial buffer size to use. + * + * @exception IOException + */ + private void init(final File file, final int bufferSize) throws IOException { + if (bufferSize <= 0) { + throw new IllegalArgumentException("Buffer size must be positive"); + } + // create a random access file for accessing the data, then create an object that's used to share + // instances of the same stream. + source = new SharedFileSource(file); + // we're opening the first one. + in = source.open(); + // this represents the entire file, for now. + start = 0; + // use the current file length for the bounds + datalen = in.length(); + // now create our buffer version + bufsize = bufferSize; + bufpos = 0; + // NB: this is using the super class protected variable. + buf = new byte[bufferSize]; + } + + + /** + * Check to see if we need to read more data into our buffer. + * + * @return False if there's not valid data in the buffer (generally means + * an EOF condition). + * @exception IOException + */ + private boolean checkFill() throws IOException { + // if we have data in the buffer currently, just return + if (pos < count) { + return true; + } + + // ugh, extending BufferedInputStream also means supporting mark positions. That complicates everything. + // life is so much easier if marks are not used.... + if (markpos < 0) { + // reset back to the buffer position + pos = 0; + // this will be the new position within the file once we're read some data. + bufpos += count; + } + else { + // we have marks to worry about....damn. + // if we have room in the buffer to read more data, then we will. Otherwise, we need to see + // if it's possible to shift the data in the buffer or extend the buffer (up to the mark limit). + if (pos >= buf.length) { + // the mark position is not at the beginning of the buffer, so just shuffle the bytes, leaving + // us room to read more data. + if (markpos > 0) { + // this is the size of the data we need to keep. + final int validSize = pos - markpos; + // perform the shift operation. + System.arraycopy(buf, markpos, buf, 0, validSize); + // now adjust the positional markers for this shift. + pos = validSize; + bufpos += markpos; + markpos = 0; + } + // the mark is at the beginning, and we've used up the buffer. See if we're allowed to + // extend this. + else if (buf.length < marklimit) { + // try to double this, but throttle to the mark limit + final int newSize = Math.min(buf.length * 2, marklimit); + + final byte[] newBuffer = new byte[newSize]; + System.arraycopy(buf, 0, newBuffer, 0, buf.length); + + // replace the old buffer. Note that all other positional markers remain the same here. + buf = newBuffer; + } + // we've got further than allowed, so invalidate the mark, and just reset the buffer + else { + markpos = -1; + pos = 0; + bufpos += count; + } + } + } + + // if we're past our designated end, force an eof. + if (bufpos + pos >= start + datalen) { + // make sure we zero the count out, otherwise we'll reuse this data + // if called again. + count = pos; + return false; + } + + // seek to the read location start. Note this is a shared file, so this assumes all of the methods + // doing buffer fills will be synchronized. + int fillLength = buf.length - pos; + + // we might be working with a subset of the file data, so normal eof processing might not apply. + // we need to limit how much we read to the data length. + if (bufpos - start + pos + fillLength > datalen) { + fillLength = (int)(datalen - (bufpos - start + pos)); + } + + // finally, try to read more data into the buffer. + fillLength = source.read(bufpos + pos, buf, pos, fillLength); + + // we weren't able to read anything, count this as an eof failure. + if (fillLength <= 0) { + // make sure we zero the count out, otherwise we'll reuse this data + // if called again. + count = pos; + return false; + } + + // set the new buffer count + count = fillLength + pos; + + // we have data in the buffer. + return true; + } + + + /** + * Return the number of bytes available for reading without + * blocking for a long period. + * + * @return For this stream, this is the number of bytes between the + * current read position and the indicated end of the file. + * @exception IOException + */ + @Override + public synchronized int available() throws IOException { + checkOpen(); + + // this is backed by a file, which doesn't really block. We can return all the way to the + // marked data end, if necessary + final long endMarker = start + datalen; + return (int)(endMarker - (bufpos + pos)); + } + + + /** + * Return the current read position of the stream. + * + * @return The current position relative to the beginning of the stream. + * This is not the position relative to the start of the file, since + * the stream starting position may be other than the beginning. + */ + public long getPosition() { + checkOpenRuntime(); + + return bufpos + pos - start; + } + + + /** + * Mark the current position for retracing. + * + * @param readlimit The limit for the distance the read position can move from + * the mark position before the mark is reset. + */ + @Override + public synchronized void mark(final int readlimit) { + checkOpenRuntime(); + marklimit = readlimit; + markpos = pos; + } + + + /** + * Read a single byte of data from the input stream. + * + * @return The read byte. Returns -1 if an eof condition has been hit. + * @exception IOException + */ + @Override + public synchronized int read() throws IOException { + checkOpen(); + + // check to see if we can fill more data + if (!checkFill()) { + return -1; + } + + // return the current byte...anded to prevent sign extension. + return buf[pos++] & 0xff; + } + + + /** + * Read multiple bytes of data and place them directly into + * a byte-array buffer. + * + * @param buffer The target buffer. + * @param offset The offset within the buffer to place the data. + * @param length The length to attempt to read. + * + * @return The number of bytes actually read. Returns -1 for an EOF + * condition. + * @exception IOException + */ + @Override + public synchronized int read(final byte buffer[], int offset, int length) throws IOException { + checkOpen(); + + // asked to read nothing? That's what we'll do. + if (length == 0) { + return 0; + } + + + int returnCount = 0; + while (length > 0) { + // check to see if we can/must fill more data + if (!checkFill()) { + // we've hit the end, but if we've read data, then return that. + if (returnCount > 0) { + return returnCount; + } + // trun eof. + return -1; + } + + final int available = count - pos; + final int given = Math.min(available, length); + + System.arraycopy(buf, pos, buffer, offset, given); + + // now adjust all of our positions and counters + pos += given; + length -= given; + returnCount += given; + offset += given; + } + // return the accumulated count. + return returnCount; + } + + + /** + * Skip the read pointer ahead a given number of bytes. + * + * @param n The number of bytes to skip. + * + * @return The number of bytes actually skipped. + * @exception IOException + */ + @Override + public synchronized long skip(final long n) throws IOException { + checkOpen(); + + // nothing to skip, so don't skip + if (n <= 0) { + return 0; + } + + // see if we need to fill more data, and potentially shift the mark positions + if (!checkFill()) { + return 0; + } + + final long available = count - pos; + + // the skipped contract allows skipping within the current buffer bounds, so cap it there. + final long skipped = available < n ? available : n; + pos += skipped; + return skipped; + } + + /** + * Reset the mark position. + * + * @exception IOException + */ + @Override + public synchronized void reset() throws IOException { + checkOpen(); + if (markpos < 0) { + throw new IOException("Resetting to invalid mark position"); + } + // if we have a markpos, it will still be in the buffer bounds. + pos = markpos; + } + + + /** + * Indicates the mark() operation is supported. + * + * @return Always returns true. + */ + @Override + public boolean markSupported() { + return true; + } + + + /** + * Close the stream. This does not close the source file until + * the last shared instance is closed. + * + * @exception IOException + */ + @Override + public void close() throws IOException { + // already closed? This is not an error + if (in == null) { + return; + } + + try { + // perform a close on the source version. + source.close(); + } finally { + in = null; + } + } + + + /** + * Create a new stream from this stream, using the given + * start offset and length. + * + * @param offset The offset relative to the start of this stream instance. + * @param end The end offset of the substream. If -1, the end of the parent stream is used. + * + * @return A new SharedFileInputStream object sharing the same source + * input file. + */ + public InputStream newStream(final long offset, long end) { + checkOpenRuntime(); + + if (offset < 0) { + throw new IllegalArgumentException("Start position is less than 0"); + } + // the default end position is the datalen of the one we're spawning from. + if (end == -1) { + end = datalen; + } + + // create a new one using the private constructor + return new SharedFileInputStream(source, start + (int)offset, (int)(end - offset), bufsize); + } + + + /** + * Check if the file is open and throw an IOException if not. + * + * @exception IOException + */ + private void checkOpen() throws IOException { + if (in == null) { + throw new IOException("Stream has been closed"); + } + } + + + /** + * Check if the file is open and throw an IOException if not. This version is + * used because several API methods are not defined as throwing IOException, so + * checkOpen() can't be used. The Sun implementation just throws RuntimeExceptions + * in those methods, hence 2 versions. + * + * @exception RuntimeException + */ + private void checkOpenRuntime() { + if (in == null) { + throw new RuntimeException("Stream has been closed"); + } + } + + + /** + * Internal class used to manage resources shared between the + * ShareFileInputStream instances. + */ + class SharedFileSource { + // the file source + public RandomAccessFile source; + // the shared instance count for this file (open instances) + public int instanceCount = 0; + + public SharedFileSource(final File file) throws IOException { + source = new RandomAccessFile(file, "r"); + } + + /** + * Open the shared stream to keep track of open instances. + */ + public synchronized RandomAccessFile open() { + instanceCount++; + return source; + } + + /** + * Process a close request for this stream. If there are multiple + * instances using this underlying stream, the stream will not + * be closed. + * + * @exception IOException + */ + public synchronized void close() throws IOException { + if (instanceCount > 0) { + instanceCount--; + // if the last open instance, close the real source file. + if (instanceCount == 0) { + source.close(); + } + } + } + + /** + * Read a buffer of data from the shared file. + * + * @param position The position to read from. + * @param buf The target buffer for storing the read data. + * @param offset The starting offset within the buffer. + * @param length The length to attempt to read. + * + * @return The number of bytes actually read. + * @exception IOException + */ + public synchronized int read(final long position, final byte[] buf, final int offset, final int length) throws IOException { + // seek to the read location start. Note this is a shared file, so this assumes all of the methods + // doing buffer fills will be synchronized. + source.seek(position); + return source.read(buf, offset, length); + } + + + /** + * Ensure the stream is closed when this shared object is finalized. + * + * @exception Throwable + */ + @Override + protected void finalize() throws Throwable { + super.finalize(); + if (instanceCount > 0) { + source.close(); + } + } + } +} + Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/Activator.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/Activator.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/Activator.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/Activator.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,101 @@ +/** + * 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.geronimo.mail; + +import java.util.ArrayList; +import java.util.List; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.log.LogService; +import org.osgi.util.tracker.BundleTracker; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +/** + * The activator that starts and manages the tracking of + * JAF activation command maps + */ +public class Activator extends org.apache.geronimo.osgi.locator.Activator { + // tracker to watch for bundle updates + protected BundleTracker bt; + // service tracker for a logging service + protected ServiceTracker lst; + // an array of all active logging services. + protected List logServices = new ArrayList(); + + @Override + public synchronized void start(final BundleContext context) throws Exception { + super.start(context); + lst = new LogServiceTracker(context, LogService.class.getName(), null); + lst.open(); + bt = new BundleTracker(context, Bundle.ACTIVE, new MailProviderBundleTrackerCustomizer(this, context.getBundle())); + bt.open(); + } + + @Override + public synchronized void stop(final BundleContext context) throws Exception { + bt.close(); + lst.close(); + super.stop(context); + } + + void log(final int level, final String message) { + synchronized (logServices) { + for (final LogService log : logServices) { + log.log(level, message); + } + } + } + + void log(final int level, final String message, final Throwable th) { + synchronized (logServices) { + for (final LogService log : logServices) { + log.log(level, message, th); + } + } + } + + private final class LogServiceTracker extends ServiceTracker { + private LogServiceTracker(final BundleContext context, final String clazz, + final ServiceTrackerCustomizer customizer) { + super(context, clazz, customizer); + } + + @Override + public Object addingService(final ServiceReference reference) { + final Object svc = super.addingService(reference); + if (svc instanceof LogService) { + synchronized (logServices) { + logServices.add((LogService) svc); + } + } + return svc; + } + + @Override + public void removedService(final ServiceReference reference, final Object service) { + synchronized (logServices) { + logServices.remove(service); + } + super.removedService(reference, service); + } + } +} Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/MailProviderBundleTrackerCustomizer.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/MailProviderBundleTrackerCustomizer.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/MailProviderBundleTrackerCustomizer.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/MailProviderBundleTrackerCustomizer.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,69 @@ +/** + * 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.geronimo.mail; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleEvent; +import org.osgi.util.tracker.BundleTrackerCustomizer; + +public class MailProviderBundleTrackerCustomizer implements BundleTrackerCustomizer { + // our base Activator (used as a service source) + private final Activator activator; + // the bundle hosting the activation code + private final Bundle activationBundle; + + public MailProviderBundleTrackerCustomizer(final Activator a, final Bundle b) { + activator = a; + activationBundle = b; + } + + /** + * Handle the activation of a new bundle. + * + * @param bundle The source bundle. + * @param event The bundle event information. + * + * @return A return object. + */ + public Object addingBundle(final Bundle bundle, final BundleEvent event) { + if (bundle.equals(activationBundle)) { + return null; + } + + return MailProviderRegistry.registerBundle(bundle); + } + + + public void modifiedBundle(final Bundle bundle, final BundleEvent event, final Object object) { + // this will update for the new bundle + MailProviderRegistry.registerBundle(bundle); + } + + public void removedBundle(final Bundle bundle, final BundleEvent event, final Object object) { + MailProviderRegistry.unregisterBundle(bundle); + } + + private void log(final int level, final String message) { + activator.log(level, message); + } + + private void log(final int level, final String message, final Throwable th) { + activator.log(level, message, th); + } +} Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/MailProviderRegistry.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/MailProviderRegistry.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/MailProviderRegistry.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/MailProviderRegistry.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,102 @@ +/** + * 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.geronimo.mail; + +import java.net.URL; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.osgi.framework.Bundle; + +/** + * The activator that starts and manages the tracking of + * JAF activation command maps + */ +public class MailProviderRegistry { + // a list of all active mail provider config files + static ConcurrentMap providers = new ConcurrentHashMap(); + // a list of all active default provider config files + static ConcurrentMap defaultProviders = new ConcurrentHashMap(); + + /** + * Perform the check for an existing mailcap file when + * a bundle is registered. + * + * @param bundle The potential provider bundle. + * + * @return A URL object if this bundle contains a mailcap file. + */ + static Object registerBundle(final Bundle bundle) { + // potential tracker return result + Object result = null; + // a given bundle might have a javamail.providers definition and/or a + // default providers definition. + URL url = bundle.getResource("META-INF/javamail.providers"); + if (url != null) { + providers.put(bundle.getBundleId(), url); + // this indicates our interest + result = url; + } + + url = bundle.getResource("META-INF/javamail.default.providers"); + if (url != null) { + defaultProviders.put(bundle.getBundleId(), url); + // this indicates our interest + result = url; + } + // the url marks our interest in additional activity for this + // bundle. + return result; + } + + + /** + * Remove a bundle from our potential mailcap pool. + * + * @param bundle The potential source bundle. + */ + static void unregisterBundle(final Bundle bundle) { + // remove these items + providers.remove(bundle.getBundleId()); + defaultProviders.remove(bundle.getBundleId()); + } + + /** + * Retrieve any located provider definitions + * from bundles. + * + * @return A collection of the provider definition file + * URLs. + */ + public static Collection getProviders() { + return providers.values(); + } + + /** + * Retrieve any located default provider definitions + * from bundles. + * + * @return A collection of the default provider definition file + * URLs. + */ + public static Collection getDefaultProviders() { + return defaultProviders.values(); + } +} Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/HtmlHandler.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,29 @@ +/* + * 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.geronimo.mail.handlers; + +import javax.activation.ActivationDataFlavor; + +public class HtmlHandler extends TextHandler { + public HtmlHandler() { + super(new ActivationDataFlavor(java.lang.String.class, "text/html", "HTML String")); + } +} + Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/MessageHandler.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,124 @@ +/* + * 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.geronimo.mail.handlers; + +import java.awt.datatransfer.DataFlavor; +import java.io.IOException; +import java.io.OutputStream; + +import javax.activation.ActivationDataFlavor; +import javax.activation.DataContentHandler; +import javax.activation.DataSource; +import javax.mail.Message; +import javax.mail.MessageAware; +import javax.mail.MessageContext; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +public class MessageHandler implements DataContentHandler { + /** + * Field dataFlavor + */ + ActivationDataFlavor dataFlavor; + + public MessageHandler(){ + dataFlavor = new ActivationDataFlavor(java.lang.String.class, "message/rfc822", "Text"); + } + + + /** + * Method getDF + * + * @return dataflavor + */ + protected ActivationDataFlavor getDF() { + return dataFlavor; + } + + /** + * Method getTransferDataFlavors + * + * @return dataflavors + */ + public DataFlavor[] getTransferDataFlavors() { + return (new DataFlavor[]{dataFlavor}); + } + + /** + * Method getTransferData + * + * @param dataflavor + * @param datasource + * @return + * @throws IOException + */ + public Object getTransferData(final DataFlavor dataflavor, final DataSource datasource) + throws IOException { + if (getDF().equals(dataflavor)) { + return getContent(datasource); + } + return null; + } + + /** + * Method getContent + * + * @param datasource + * @return + * @throws IOException + */ + public Object getContent(final DataSource datasource) throws IOException { + + try { + // if this is a proper message, it implements the MessageAware interface. We need this to + // get the associated session. + if (datasource instanceof MessageAware) { + final MessageContext context = ((MessageAware)datasource).getMessageContext(); + // construct a mime message instance from the stream, associating it with the + // data source session. + return new MimeMessage(context.getSession(), datasource.getInputStream()); + } + } catch (final MessagingException e) { + // we need to transform any exceptions into an IOException. + throw new IOException("Exception writing MimeMultipart: " + e.toString()); + } + return null; + } + + /** + * Method writeTo + * + * @param object + * @param s + * @param outputstream + * @throws IOException + */ + public void writeTo(final Object object, final String s, final OutputStream outputstream) throws IOException { + // proper message type? + if (object instanceof Message) { + try { + ((Message)object).writeTo(outputstream); + } catch (final MessagingException e) { + throw new IOException("Error parsing message: " + e.toString()); + } + } + } +} + Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/MultipartHandler.java Tue Aug 26 18:17:06 2014 @@ -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.geronimo.mail.handlers; + +import java.awt.datatransfer.DataFlavor; +import java.io.IOException; +import java.io.OutputStream; + +import javax.activation.ActivationDataFlavor; +import javax.activation.DataContentHandler; +import javax.activation.DataSource; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMultipart; + +public class MultipartHandler implements DataContentHandler { + /** + * Field dataFlavor + */ + ActivationDataFlavor dataFlavor; + + public MultipartHandler(){ + dataFlavor = new ActivationDataFlavor(javax.mail.internet.MimeMultipart.class, "multipart/mixed", "Multipart"); + } + + /** + * Constructor TextHandler + * + * @param dataFlavor + */ + public MultipartHandler(final ActivationDataFlavor dataFlavor) { + this.dataFlavor = dataFlavor; + } + + /** + * Method getDF + * + * @return dataflavor + */ + protected ActivationDataFlavor getDF() { + return dataFlavor; + } + + /** + * Method getTransferDataFlavors + * + * @return dataflavors + */ + public DataFlavor[] getTransferDataFlavors() { + return (new DataFlavor[]{dataFlavor}); + } + + /** + * Method getTransferData + * + * @param dataflavor + * @param datasource + * @return + * @throws IOException + */ + public Object getTransferData(final DataFlavor dataflavor, final DataSource datasource) + throws IOException { + if (getDF().equals(dataflavor)) { + return getContent(datasource); + } + return null; + } + + /** + * Method getContent + * + * @param datasource + * @return + * @throws IOException + */ + public Object getContent(final DataSource datasource) throws IOException { + try { + return new MimeMultipart(datasource); + } catch (final MessagingException e) { + // if there is a syntax error from the datasource parsing, the content is + // just null. + return null; + } + } + + /** + * Method writeTo + * + * @param object + * @param s + * @param outputstream + * @throws IOException + */ + public void writeTo(final Object object, final String s, final OutputStream outputstream) throws IOException { + // if this object is a MimeMultipart, then delegate to the part. + if (object instanceof MimeMultipart) { + try { + ((MimeMultipart)object).writeTo(outputstream); + } catch (final MessagingException e) { + // we need to transform any exceptions into an IOException. + throw new IOException("Exception writing MimeMultipart: " + e.toString()); + } + } + } +} Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/TextHandler.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,160 @@ +/* + * 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.geronimo.mail.handlers; + +import java.awt.datatransfer.DataFlavor; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; + +import javax.activation.ActivationDataFlavor; +import javax.activation.DataContentHandler; +import javax.activation.DataSource; +import javax.mail.internet.ContentType; +import javax.mail.internet.MimeUtility; +import javax.mail.internet.ParseException; + +public class TextHandler implements DataContentHandler { + /** + * Field dataFlavor + */ + ActivationDataFlavor dataFlavor; + + public TextHandler(){ + dataFlavor = new ActivationDataFlavor(java.lang.String.class, "text/plain", "Text String"); + } + + /** + * Constructor TextHandler + * + * @param dataFlavor + */ + public TextHandler(final ActivationDataFlavor dataFlavor) { + this.dataFlavor = dataFlavor; + } + + /** + * Method getDF + * + * @return dataflavor + */ + protected ActivationDataFlavor getDF() { + return dataFlavor; + } + + /** + * Method getTransferDataFlavors + * + * @return dataflavors + */ + public DataFlavor[] getTransferDataFlavors() { + return (new DataFlavor[]{dataFlavor}); + } + + /** + * Method getTransferData + * + * @param dataflavor + * @param datasource + * @return + * @throws IOException + */ + public Object getTransferData(final DataFlavor dataflavor, final DataSource datasource) + throws IOException { + if (getDF().equals(dataflavor)) { + return getContent(datasource); + } + return null; + } + + /** + * Method getContent + * + * @param datasource + * @return + * @throws IOException + */ + public Object getContent(final DataSource datasource) throws IOException { + final InputStream is = datasource.getInputStream(); + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + + int count; + final byte[] buffer = new byte[1000]; + + try { + while ((count = is.read(buffer, 0, buffer.length)) > 0) { + os.write(buffer, 0, count); + } + } finally { + is.close(); + } + try { + return os.toString(getCharSet(datasource.getContentType())); + } catch (final ParseException e) { + throw new UnsupportedEncodingException(e.getMessage()); + } + } + + + /** + * Write an object of "our" type out to the provided + * output stream. The content type might modify the + * result based on the content type parameters. + * + * @param object The object to write. + * @param contentType + * The content mime type, including parameters. + * @param outputstream + * The target output stream. + * + * @throws IOException + */ + public void writeTo(final Object object, final String contentType, final OutputStream outputstream) + throws IOException { + OutputStreamWriter os; + try { + final String charset = getCharSet(contentType); + os = new OutputStreamWriter(outputstream, charset); + } catch (final Exception ex) { + throw new UnsupportedEncodingException(ex.toString()); + } + final String content = (String) object; + os.write(content, 0, content.length()); + os.flush(); + } + + /** + * get the character set from content type + * @param contentType + * @return + * @throws ParseException + */ + protected String getCharSet(final String contentType) throws ParseException { + final ContentType type = new ContentType(contentType); + String charset = type.getParameter("charset"); + if (charset == null) { + charset = "us-ascii"; + } + return MimeUtility.javaCharset(charset); + } +} Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/handlers/XMLHandler.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,28 @@ +/* + * 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.geronimo.mail.handlers; + +import javax.activation.ActivationDataFlavor; + +public class XMLHandler extends TextHandler { + public XMLHandler() { + super(new ActivationDataFlavor(java.lang.String.class, "text/xml", "XML String")); + } +} Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/ASCIIUtil.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,246 @@ +/* + * 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.geronimo.mail.util; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Set of utility classes for handling common encoding-related + * manipulations. + */ +public class ASCIIUtil { + + /** + * Test to see if this string contains only US-ASCII (i.e., 7-bit + * ASCII) charactes. + * + * @param s The test string. + * + * @return true if this is a valid 7-bit ASCII encoding, false if it + * contains any non-US ASCII characters. + */ + static public boolean isAscii(final String s) { + for (int i = 0; i < s.length(); i++) { + if (!isAscii(s.charAt(i))) { + return false; + } + } + return true; + } + + /** + * Test to see if a given character can be considered "valid" ASCII. + * The excluded characters are the control characters less than + * 32, 8-bit characters greater than 127, EXCEPT the CR, LF and + * tab characters ARE considered value (all less than 32). + * + * @param ch The test character. + * + * @return true if this character meets the "ascii-ness" criteria, false + * otherwise. + */ + static public boolean isAscii(final int ch) { + // these are explicitly considered valid. + if (ch == '\r' || ch == '\n' || ch == '\t') { + return true; + } + + // anything else outside the range is just plain wrong. + if (ch >= 127 || ch < 32) { + return false; + } + return true; + } + + + /** + * Examine a stream of text and make a judgement on what encoding + * type should be used for the text. Ideally, we want to use 7bit + * encoding to determine this, but we may need to use either quoted-printable + * or base64. The choice is made on the ratio of 7-bit characters to non-7bit. + * + * @param content An input stream for the content we're examining. + * + * @exception IOException + */ + public static String getTextTransferEncoding(final InputStream content) throws IOException { + + // for efficiency, we'll read in blocks. + final BufferedInputStream in = new BufferedInputStream(content, 4096); + + int span = 0; // span of characters without a line break. + boolean containsLongLines = false; + int asciiChars = 0; + int nonAsciiChars = 0; + + while (true) { + final int ch = in.read(); + // if we hit an EOF here, go decide what type we've actually found. + if (ch == -1) { + break; + } + + // we found a linebreak. Reset the line length counters on either one. We don't + // really need to validate here. + if (ch == '\n' || ch == '\r') { + // hit a line end, reset our line length counter + span = 0; + } + else { + span++; + // the text has long lines, we can't transfer this as unencoded text. + if (span > 998) { + containsLongLines = true; + } + + // non-ascii character, we have to transfer this in binary. + if (!isAscii(ch)) { + nonAsciiChars++; + } + else { + asciiChars++; + } + } + } + + // looking good so far, only valid chars here. + if (nonAsciiChars == 0) { + // does this contain long text lines? We need to use a Q-P encoding which will + // be only slightly longer, but handles folding the longer lines. + if (containsLongLines) { + return "quoted-printable"; + } + else { + // ideal! Easiest one to handle. + return "7bit"; + } + } + else { + // mostly characters requiring encoding? Base64 is our best bet. + if (nonAsciiChars > asciiChars) { + return "base64"; + } + else { + // Q-P encoding will use fewer bytes than the full Base64. + return "quoted-printable"; + } + } + } + + + /** + * Examine a stream of text and make a judgement on what encoding + * type should be used for the text. Ideally, we want to use 7bit + * encoding to determine this, but we may need to use either quoted-printable + * or base64. The choice is made on the ratio of 7-bit characters to non-7bit. + * + * @param content A string for the content we're examining. + */ + public static String getTextTransferEncoding(final String content) { + + int asciiChars = 0; + int nonAsciiChars = 0; + + for (int i = 0; i < content.length(); i++) { + final int ch = content.charAt(i); + + // non-ascii character, we have to transfer this in binary. + if (!isAscii(ch)) { + nonAsciiChars++; + } + else { + asciiChars++; + } + } + + // looking good so far, only valid chars here. + if (nonAsciiChars == 0) { + // ideal! Easiest one to handle. + return "7bit"; + } + else { + // mostly characters requiring encoding? Base64 is our best bet. + if (nonAsciiChars > asciiChars) { + return "base64"; + } + else { + // Q-P encoding will use fewer bytes than the full Base64. + return "quoted-printable"; + } + } + } + + + /** + * Determine if the transfer encoding looks like it might be + * valid ascii text, and thus transferable as 7bit code. In + * order for this to be true, all characters must be valid + * 7-bit ASCII code AND all line breaks must be properly formed + * (JUST '\r\n' sequences). 7-bit transfers also + * typically have a line limit of 1000 bytes (998 + the CRLF), so any + * stretch of charactes longer than that will also force Base64 encoding. + * + * @param content An input stream for the content we're examining. + * + * @exception IOException + */ + public static String getBinaryTransferEncoding(final InputStream content) throws IOException { + + // for efficiency, we'll read in blocks. + final BufferedInputStream in = new BufferedInputStream(content, 4096); + + int previousChar = 0; + int span = 0; // span of characters without a line break. + + while (true) { + final int ch = in.read(); + // if we hit an EOF here, we've only found valid text so far, so we can transfer this as + // 7-bit ascii. + if (ch == -1) { + return "7bit"; + } + + // we found a newline, this is only valid if the previous char was the '\r' + if (ch == '\n') { + // malformed linebreak? force this to base64 encoding. + if (previousChar != '\r') { + return "base64"; + } + // hit a line end, reset our line length counter + span = 0; + } + else { + span++; + // the text has long lines, we can't transfer this as unencoded text. + if (span > 998) { + return "base64"; + } + + // non-ascii character, we have to transfer this in binary. + if (!isAscii(ch)) { + return "base64"; + } + } + previousChar = ch; + } + } +} Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/Base64.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/Base64.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/Base64.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/Base64.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,189 @@ +/* + * 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.geronimo.mail.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class Base64 +{ + private static final Encoder encoder = new Base64Encoder(); + + /** + * encode the input data producing a base 64 encoded byte array. + * + * @return a byte array containing the base 64 encoded data. + */ + public static byte[] encode( + final byte[] data) + { + // just forward to the general array encoder. + return encode(data, 0, data.length); + } + + /** + * encode the input data producing a base 64 encoded byte array. + * + * @param data The data array to encode. + * @param offset The starting offset within the data array. + * @param length The length of the data to encode. + * + * @return a byte array containing the base 64 encoded data. + */ + public static byte[] encode( + final byte[] data, + final int offset, + final int length) + { + final ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.encode(data, 0, data.length, bOut); + } + catch (final IOException e) + { + throw new RuntimeException("exception encoding base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * Encode the byte data to base 64 writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + final byte[] data, + final OutputStream out) + throws IOException + { + return encoder.encode(data, 0, data.length, out); + } + + /** + * Encode the byte data to base 64 writing it to the given output stream. + * + * @return the number of bytes produced. + */ + public static int encode( + final byte[] data, + final int off, + final int length, + final OutputStream out) + throws IOException + { + return encoder.encode(data, off, length, out); + } + + /** + * decode the base 64 encoded input data. It is assumed the input data is valid. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + final byte[] data) + { + // just decode the entire array of data. + return decode(data, 0, data.length); + } + + + /** + * decode the base 64 encoded input data. It is assumed the input data is valid. + * + * @param data The data array to decode. + * @param offset The offset of the data array. + * @param length The length of data to decode. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + final byte[] data, + final int offset, + final int length) + { + final ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.decode(data, offset, length, bOut); + } + catch (final IOException e) + { + throw new RuntimeException("exception decoding base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * decode the base 64 encoded String data - whitespace will be ignored. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + final String data) + { + final ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + try + { + encoder.decode(data, bOut); + } + catch (final IOException e) + { + throw new RuntimeException("exception decoding base64 string: " + e); + } + + return bOut.toByteArray(); + } + + /** + * decode the base 64 encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @return the number of bytes produced. + */ + public static int decode( + final String data, + final OutputStream out) + throws IOException + { + return encoder.decode(data, out); + } + + /** + * decode the base 64 encoded String data writing it to the given output stream, + * whitespace characters will be ignored. + * + * @param data The array data to decode. + * @param out The output stream for the data. + * + * @return the number of bytes produced. + * @exception IOException + */ + public static int decode(final byte [] data, final OutputStream out) throws IOException + { + return encoder.decode(data, 0, data.length, out); + } +} Added: geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java URL: http://svn.apache.org/viewvc/geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java?rev=1620683&view=auto ============================================================================== --- geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java (added) +++ geronimo/specs/trunk/geronimo-javamail_1.5_spec/src/main/java/org/apache/geronimo/mail/util/Base64DecoderStream.java Tue Aug 26 18:17:06 2014 @@ -0,0 +1,215 @@ +/* + * 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.geronimo.mail.util; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * An implementation of a FilterInputStream that decodes the + * stream data in BASE64 encoding format. This version does the + * decoding "on the fly" rather than decoding a single block of + * data. Since this version is intended for use by the MimeUtilty class, + * it also handles line breaks in the encoded data. + */ +public class Base64DecoderStream extends FilterInputStream { + + static protected final String MAIL_BASE64_IGNOREERRORS = "mail.mime.base64.ignoreerrors"; + + // number of decodeable units we'll try to process at one time. We'll attempt to read that much + // data from the input stream and decode in blocks. + static protected final int BUFFERED_UNITS = 2000; + + // our decoder for processing the data + protected Base64Encoder decoder = new Base64Encoder(); + + // can be overridden by a system property. + protected boolean ignoreErrors = false; + + // buffer for reading in chars for decoding (which can support larger bulk reads) + protected byte[] encodedChars = new byte[BUFFERED_UNITS * 4]; + // a buffer for one decoding unit's worth of data (3 bytes). This is the minimum amount we + // can read at one time. + protected byte[] decodedChars = new byte[BUFFERED_UNITS * 3]; + // count of characters in the buffer + protected int decodedCount = 0; + // index of the next decoded character + protected int decodedIndex = 0; + + + public Base64DecoderStream(final InputStream in) { + super(in); + // make sure we get the ignore errors flag + ignoreErrors = SessionUtil.getBooleanProperty(MAIL_BASE64_IGNOREERRORS, false); + } + + /** + * Test for the existance of decoded characters in our buffer + * of decoded data. + * + * @return True if we currently have buffered characters. + */ + private boolean dataAvailable() { + return decodedCount != 0; + } + + /** + * Get the next buffered decoded character. + * + * @return The next decoded character in the buffer. + */ + private byte getBufferedChar() { + decodedCount--; + return decodedChars[decodedIndex++]; + } + + /** + * Decode a requested number of bytes of data into a buffer. + * + * @return true if we were able to obtain more data, false otherwise. + */ + private boolean decodeStreamData() throws IOException { + decodedIndex = 0; + + // fill up a data buffer with input data + final int readCharacters = fillEncodedBuffer(); + + if (readCharacters > 0) { + decodedCount = decoder.decode(encodedChars, 0, readCharacters, decodedChars); + return true; + } + return false; + } + + + /** + * Retrieve a single byte from the decoded characters buffer. + * + * @return The decoded character or -1 if there was an EOF condition. + */ + private int getByte() throws IOException { + if (!dataAvailable()) { + if (!decodeStreamData()) { + return -1; + } + } + decodedCount--; + // we need to ensure this doesn't get sign extended + return decodedChars[decodedIndex++] & 0xff; + } + + private int getBytes(final byte[] data, int offset, int length) throws IOException { + + int readCharacters = 0; + while (length > 0) { + // need data? Try to get some + if (!dataAvailable()) { + // if we can't get this, return a count of how much we did get (which may be -1). + if (!decodeStreamData()) { + return readCharacters > 0 ? readCharacters : -1; + } + } + + // now copy some of the data from the decoded buffer to the target buffer + final int copyCount = Math.min(decodedCount, length); + System.arraycopy(decodedChars, decodedIndex, data, offset, copyCount); + decodedIndex += copyCount; + decodedCount -= copyCount; + offset += copyCount; + length -= copyCount; + readCharacters += copyCount; + } + return readCharacters; + } + + + /** + * Fill our buffer of input characters for decoding from the + * stream. This will attempt read a full buffer, but will + * terminate on an EOF or read error. This will filter out + * non-Base64 encoding chars and will only return a valid + * multiple of 4 number of bytes. + * + * @return The count of characters read. + */ + private int fillEncodedBuffer() throws IOException + { + int readCharacters = 0; + + while (true) { + // get the next character from the stream + final int ch = in.read(); + // did we hit an EOF condition? + if (ch == -1) { + // now check to see if this is normal, or potentially an error + // if we didn't get characters as a multiple of 4, we may need to complain about this. + if ((readCharacters % 4) != 0) { + // the error checking can be turned off...normally it isn't + if (!ignoreErrors) { + throw new IOException("Base64 encoding error, data truncated"); + } + // we're ignoring errors, so round down to a multiple and return that. + return (readCharacters / 4) * 4; + } + // return the count. + return readCharacters; + } + // if this character is valid in a Base64 stream, copy it to the buffer. + else if (decoder.isValidBase64(ch)) { + encodedChars[readCharacters++] = (byte)ch; + // if we've filled up the buffer, time to quit. + if (readCharacters >= encodedChars.length) { + return readCharacters; + } + } + + // we're filtering out whitespace and CRLF characters, so just ignore these + } + } + + + // in order to function as a filter, these streams need to override the different + // read() signature. + + @Override + public int read() throws IOException + { + return getByte(); + } + + + @Override + public int read(final byte [] buffer, final int offset, final int length) throws IOException { + return getBytes(buffer, offset, length); + } + + + @Override + public boolean markSupported() { + return false; + } + + + @Override + public int available() throws IOException { + return ((in.available() / 4) * 3) + decodedCount; + } +}