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 020package org.apache.james.mime4j.message; 021 022import java.io.IOException; 023import java.io.InputStream; 024 025import org.apache.james.mime4j.MimeException; 026import org.apache.james.mime4j.MimeIOException; 027import org.apache.james.mime4j.codec.DecodeMonitor; 028import org.apache.james.mime4j.dom.Body; 029import org.apache.james.mime4j.dom.Disposable; 030import org.apache.james.mime4j.dom.Entity; 031import org.apache.james.mime4j.dom.FieldParser; 032import org.apache.james.mime4j.dom.Header; 033import org.apache.james.mime4j.dom.Message; 034import org.apache.james.mime4j.dom.MessageBuilder; 035import org.apache.james.mime4j.dom.Multipart; 036import org.apache.james.mime4j.dom.SingleBody; 037import org.apache.james.mime4j.dom.field.ParsedField; 038import org.apache.james.mime4j.field.DefaultFieldParser; 039import org.apache.james.mime4j.field.LenientFieldParser; 040import org.apache.james.mime4j.parser.AbstractContentHandler; 041import org.apache.james.mime4j.parser.MimeStreamParser; 042import org.apache.james.mime4j.stream.BodyDescriptorBuilder; 043import org.apache.james.mime4j.stream.Field; 044import org.apache.james.mime4j.stream.MimeConfig; 045 046/** 047 * Default implementation of {@link MessageBuilder}. 048 */ 049public 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}