001    /****************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one   *
003     * or more contributor license agreements.  See the NOTICE file *
004     * distributed with this work for additional information        *
005     * regarding copyright ownership.  The ASF licenses this file   *
006     * to you under the Apache License, Version 2.0 (the            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  You may obtain a copy of the License at   *
009     *                                                              *
010     *   http://www.apache.org/licenses/LICENSE-2.0                 *
011     *                                                              *
012     * Unless required by applicable law or agreed to in writing,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    
020    package org.apache.james.mime4j.message;
021    
022    import java.io.IOException;
023    import java.io.InputStream;
024    
025    import org.apache.james.mime4j.MimeException;
026    import org.apache.james.mime4j.MimeIOException;
027    import org.apache.james.mime4j.codec.DecodeMonitor;
028    import org.apache.james.mime4j.dom.Body;
029    import org.apache.james.mime4j.dom.Disposable;
030    import org.apache.james.mime4j.dom.Entity;
031    import org.apache.james.mime4j.dom.FieldParser;
032    import org.apache.james.mime4j.dom.Header;
033    import org.apache.james.mime4j.dom.Message;
034    import org.apache.james.mime4j.dom.MessageBuilder;
035    import org.apache.james.mime4j.dom.Multipart;
036    import org.apache.james.mime4j.dom.SingleBody;
037    import org.apache.james.mime4j.dom.field.ParsedField;
038    import org.apache.james.mime4j.field.DefaultFieldParser;
039    import org.apache.james.mime4j.field.LenientFieldParser;
040    import org.apache.james.mime4j.parser.AbstractContentHandler;
041    import org.apache.james.mime4j.parser.MimeStreamParser;
042    import org.apache.james.mime4j.stream.BodyDescriptorBuilder;
043    import org.apache.james.mime4j.stream.Field;
044    import org.apache.james.mime4j.stream.MimeConfig;
045    
046    /**
047     * Default implementation of {@link MessageBuilder}.
048     */
049    public class DefaultMessageBuilder implements MessageBuilder {
050    
051        private FieldParser<? extends ParsedField> fieldParser = null;
052        private BodyFactory bodyFactory = null;
053        private MimeConfig config = null;
054        private BodyDescriptorBuilder bodyDescBuilder = null;
055        private boolean contentDecoding = true;
056        private boolean flatMode = false;
057        private DecodeMonitor monitor = null;
058    
059        public DefaultMessageBuilder() {
060            super();
061        }
062    
063        public void setFieldParser(final FieldParser<? extends ParsedField> fieldParser) {
064            this.fieldParser = fieldParser;
065        }
066    
067        public void setBodyFactory(final BodyFactory bodyFactory) {
068            this.bodyFactory = bodyFactory;
069        }
070    
071        public void setMimeEntityConfig(final MimeConfig config) {
072            this.config = config;
073        }
074    
075        public void setBodyDescriptorBuilder(final BodyDescriptorBuilder bodyDescBuilder) {
076            this.bodyDescBuilder  = bodyDescBuilder;
077        }
078    
079        public void setDecodeMonitor(final DecodeMonitor monitor) {
080            this.monitor = monitor;
081        }
082    
083        public void setContentDecoding(boolean contentDecoding) {
084            this.contentDecoding = contentDecoding;
085        }
086    
087        public void setFlatMode(boolean flatMode) {
088            this.flatMode = flatMode;
089        }
090    
091        /**
092         * Creates a new <code>Header</code> from the specified
093         * <code>Header</code>. The <code>Header</code> instance is initialized
094         * with a copy of the list of {@link Field}s of the specified
095         * <code>Header</code>. The <code>Field</code> objects are not copied
096         * because they are immutable and can safely be shared between headers.
097         *
098         * @param other
099         *            header to copy.
100         */
101        public Header copy(Header other) {
102            HeaderImpl copy = new HeaderImpl();
103            for (Field otherField : other.getFields()) {
104                copy.addField(otherField);
105            }
106            return copy;
107        }
108    
109        /**
110         * Creates a new <code>BodyPart</code> from the specified
111         * <code>Entity</code>. The <code>BodyPart</code> instance is initialized
112         * with copies of header and body of the specified <code>Entity</code>.
113         * The parent entity of the new body part is <code>null</code>.
114         *
115         * @param other
116         *            body part to copy.
117         * @throws UnsupportedOperationException
118         *             if <code>other</code> contains a {@link SingleBody} that
119         *             does not support the {@link SingleBody#copy() copy()}
120         *             operation.
121         * @throws IllegalArgumentException
122         *             if <code>other</code> contains a <code>Body</code> that
123         *             is neither a {@link Message}, {@link Multipart} or
124         *             {@link SingleBody}.
125         */
126        public BodyPart copy(Entity other) {
127            BodyPart copy = new BodyPart();
128            if (other.getHeader() != null) {
129                copy.setHeader(copy(other.getHeader()));
130            }
131            if (other.getBody() != null) {
132                copy.setBody(copy(other.getBody()));
133            }
134            return copy;
135        }
136    
137        /**
138         * Creates a new <code>Multipart</code> from the specified
139         * <code>Multipart</code>. The <code>Multipart</code> instance is
140         * initialized with copies of preamble, epilogue, sub type and the list of
141         * body parts of the specified <code>Multipart</code>. The parent entity
142         * of the new multipart is <code>null</code>.
143         *
144         * @param other
145         *            multipart to copy.
146         * @throws UnsupportedOperationException
147         *             if <code>other</code> contains a {@link SingleBody} that
148         *             does not support the {@link SingleBody#copy() copy()}
149         *             operation.
150         * @throws IllegalArgumentException
151         *             if <code>other</code> contains a <code>Body</code> that
152         *             is neither a {@link Message}, {@link Multipart} or
153         *             {@link SingleBody}.
154         */
155        public Multipart copy(Multipart other) {
156            MultipartImpl copy = new MultipartImpl(other.getSubType());
157            for (Entity otherBodyPart : other.getBodyParts()) {
158                copy.addBodyPart(copy(otherBodyPart));
159            }
160            copy.setPreamble(other.getPreamble());
161            copy.setEpilogue(other.getEpilogue());
162            return copy;
163        }
164    
165    
166        /**
167         * Returns a copy of the given {@link Body} that can be used (and modified)
168         * independently of the original. The copy should be
169         * {@link Disposable#dispose() disposed of} when it is no longer needed.
170         * <p>
171         * The {@link Body#getParent() parent} of the returned copy is
172         * <code>null</code>, that is, the copy is detached from the parent
173         * entity of the original.
174         *
175         * @param body
176         *            body to copy.
177         * @return a copy of the given body.
178         * @throws UnsupportedOperationException
179         *             if <code>body</code> is an instance of {@link SingleBody}
180         *             that does not support the {@link SingleBody#copy() copy()}
181         *             operation (or contains such a <code>SingleBody</code>).
182         * @throws IllegalArgumentException
183         *             if <code>body</code> is <code>null</code> or
184         *             <code>body</code> is a <code>Body</code> that is neither
185         *             a {@link MessageImpl}, {@link Multipart} or {@link SingleBody}
186         *             (or contains such a <code>Body</code>).
187         */
188        public Body copy(Body body) {
189            if (body == null)
190                throw new IllegalArgumentException("Body is null");
191    
192            if (body instanceof Message)
193                return copy((Message) body);
194    
195            if (body instanceof Multipart)
196                return copy((Multipart) body);
197    
198            if (body instanceof SingleBody)
199                return ((SingleBody) body).copy();
200    
201            throw new IllegalArgumentException("Unsupported body class");
202        }
203    
204        /**
205         * Creates a new <code>Message</code> from the specified
206         * <code>Message</code>. The <code>Message</code> instance is
207         * initialized with copies of header and body of the specified
208         * <code>Message</code>. The parent entity of the new message is
209         * <code>null</code>.
210         *
211         * @param other
212         *            message to copy.
213         * @throws UnsupportedOperationException
214         *             if <code>other</code> contains a {@link SingleBody} that
215         *             does not support the {@link SingleBody#copy() copy()}
216         *             operation.
217         * @throws IllegalArgumentException
218         *             if <code>other</code> contains a <code>Body</code> that
219         *             is neither a {@link MessageImpl}, {@link Multipart} or
220         *             {@link SingleBody}.
221         */
222        public Message copy(Message other) {
223            MessageImpl copy = new MessageImpl();
224            if (other.getHeader() != null) {
225                copy.setHeader(copy(other.getHeader()));
226            }
227            if (other.getBody() != null) {
228                copy.setBody(copy(other.getBody()));
229            }
230            return copy;
231        }
232    
233        public Header newHeader() {
234            return new HeaderImpl();
235        }
236    
237        public Header newHeader(final Header source) {
238            return copy(source);
239        }
240    
241        public Multipart newMultipart(final String subType) {
242            return new MultipartImpl(subType);
243        }
244    
245        public Multipart newMultipart(final Multipart source) {
246            return copy(source);
247        }
248    
249        public Header parseHeader(final InputStream is) throws IOException, MimeIOException {
250            final MimeConfig cfg = config != null ? config : new MimeConfig();
251            boolean strict = cfg.isStrictParsing();
252            final DecodeMonitor mon = monitor != null ? monitor :
253                strict ? DecodeMonitor.STRICT : DecodeMonitor.SILENT;
254            final FieldParser<? extends ParsedField> fp = fieldParser != null ? fieldParser :
255                strict ? DefaultFieldParser.getParser() : LenientFieldParser.getParser();
256            final HeaderImpl header = new HeaderImpl();
257            final MimeStreamParser parser = new MimeStreamParser();
258            parser.setContentHandler(new AbstractContentHandler() {
259                @Override
260                public void endHeader() {
261                    parser.stop();
262                }
263                @Override
264                public void field(Field field) throws MimeException {
265                    ParsedField parsedField;
266                    if (field instanceof ParsedField) {
267                        parsedField = (ParsedField) field;
268                    } else {
269                        parsedField = fp.parse(field, mon);
270                    }
271                    header.addField(parsedField);
272                }
273            });
274            try {
275                parser.parse(is);
276            } catch (MimeException ex) {
277                throw new MimeIOException(ex);
278            }
279            return header;
280        }
281    
282        public Message newMessage() {
283            return new MessageImpl();
284        }
285    
286        public Message newMessage(final Message source) {
287            return copy(source);
288        }
289    
290        public Message parseMessage(final InputStream is) throws IOException, MimeIOException {
291            try {
292                MessageImpl message = new MessageImpl();
293                MimeConfig cfg = config != null ? config : new MimeConfig();
294                boolean strict = cfg.isStrictParsing();
295                DecodeMonitor mon = monitor != null ? monitor :
296                    strict ? DecodeMonitor.STRICT : DecodeMonitor.SILENT;
297                BodyDescriptorBuilder bdb = bodyDescBuilder != null ? bodyDescBuilder :
298                    new DefaultBodyDescriptorBuilder(null, fieldParser != null ? fieldParser :
299                        strict ? DefaultFieldParser.getParser() : LenientFieldParser.getParser(), mon);
300                BodyFactory bf = bodyFactory != null ? bodyFactory : new BasicBodyFactory();
301                MimeStreamParser parser = new MimeStreamParser(cfg, mon, bdb);
302                // EntityBuilder expect the parser will send ParserFields for the well known fields
303                // It will throw exceptions, otherwise.
304                parser.setContentHandler(new EntityBuilder(message, bf));
305                parser.setContentDecoding(contentDecoding);
306                if (flatMode) {
307                    parser.setFlat();
308                } else {
309                    parser.setRecurse();
310                }
311                parser.parse(is);
312                return message;
313            } catch (MimeException e) {
314                throw new MimeIOException(e);
315            }
316        }
317    
318    }