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.util.Collections; 023 import java.util.Date; 024 import java.util.HashMap; 025 import java.util.Map; 026 027 import org.apache.james.mime4j.field.ContentDispositionField; 028 import org.apache.james.mime4j.field.ContentTransferEncodingField; 029 import org.apache.james.mime4j.field.ContentTypeField; 030 import org.apache.james.mime4j.field.FieldName; 031 import org.apache.james.mime4j.field.Fields; 032 import org.apache.james.mime4j.parser.Field; 033 import org.apache.james.mime4j.util.MimeUtil; 034 035 /** 036 * MIME entity. An entity has a header and a body (see RFC 2045). 037 */ 038 public abstract class Entity implements Disposable { 039 private Header header = null; 040 private Body body = null; 041 private Entity parent = null; 042 043 /** 044 * Creates a new <code>Entity</code>. Typically invoked implicitly by a 045 * subclass constructor. 046 */ 047 protected Entity() { 048 } 049 050 /** 051 * Creates a new <code>Entity</code> from the specified 052 * <code>Entity</code>. The <code>Entity</code> instance is initialized 053 * with copies of header and body of the specified <code>Entity</code>. 054 * The parent entity of the new entity is <code>null</code>. 055 * 056 * @param other 057 * entity to copy. 058 * @throws UnsupportedOperationException 059 * if <code>other</code> contains a {@link SingleBody} that 060 * does not support the {@link SingleBody#copy() copy()} 061 * operation. 062 * @throws IllegalArgumentException 063 * if <code>other</code> contains a <code>Body</code> that 064 * is neither a {@link Message}, {@link Multipart} or 065 * {@link SingleBody}. 066 */ 067 protected Entity(Entity other) { 068 if (other.header != null) { 069 header = new Header(other.header); 070 } 071 072 if (other.body != null) { 073 Body bodyCopy = BodyCopier.copy(other.body); 074 setBody(bodyCopy); 075 } 076 } 077 078 /** 079 * Gets the parent entity of this entity. 080 * Returns <code>null</code> if this is the root entity. 081 * 082 * @return the parent or <code>null</code>. 083 */ 084 public Entity getParent() { 085 return parent; 086 } 087 088 /** 089 * Sets the parent entity of this entity. 090 * 091 * @param parent the parent entity or <code>null</code> if 092 * this will be the root entity. 093 */ 094 public void setParent(Entity parent) { 095 this.parent = parent; 096 } 097 098 /** 099 * Gets the entity header. 100 * 101 * @return the header. 102 */ 103 public Header getHeader() { 104 return header; 105 } 106 107 /** 108 * Sets the entity header. 109 * 110 * @param header the header. 111 */ 112 public void setHeader(Header header) { 113 this.header = header; 114 } 115 116 /** 117 * Gets the body of this entity. 118 * 119 * @return the body, 120 */ 121 public Body getBody() { 122 return body; 123 } 124 125 /** 126 * Sets the body of this entity. 127 * 128 * @param body the body. 129 * @throws IllegalStateException if the body has already been set. 130 */ 131 public void setBody(Body body) { 132 if (this.body != null) 133 throw new IllegalStateException("body already set"); 134 135 this.body = body; 136 body.setParent(this); 137 } 138 139 /** 140 * Removes and returns the body of this entity. The removed body may be 141 * attached to another entity. If it is no longer needed it should be 142 * {@link Disposable#dispose() disposed} of. 143 * 144 * @return the removed body or <code>null</code> if no body was set. 145 */ 146 public Body removeBody() { 147 if (body == null) 148 return null; 149 150 Body body = this.body; 151 this.body = null; 152 body.setParent(null); 153 154 return body; 155 } 156 157 /** 158 * Sets the specified message as body of this entity and the content type to 159 * "message/rfc822". A <code>Header</code> is created if this 160 * entity does not already have one. 161 * 162 * @param message 163 * the message to set as body. 164 */ 165 public void setMessage(Message message) { 166 setBody(message, "message/rfc822", null); 167 } 168 169 /** 170 * Sets the specified multipart as body of this entity. Also sets the 171 * content type accordingly and creates a message boundary string. A 172 * <code>Header</code> is created if this entity does not already have 173 * one. 174 * 175 * @param multipart 176 * the multipart to set as body. 177 */ 178 public void setMultipart(Multipart multipart) { 179 String mimeType = "multipart/" + multipart.getSubType(); 180 Map<String, String> parameters = Collections.singletonMap("boundary", 181 MimeUtil.createUniqueBoundary()); 182 183 setBody(multipart, mimeType, parameters); 184 } 185 186 /** 187 * Sets the specified multipart as body of this entity. Also sets the 188 * content type accordingly and creates a message boundary string. A 189 * <code>Header</code> is created if this entity does not already have 190 * one. 191 * 192 * @param multipart 193 * the multipart to set as body. 194 * @param parameters 195 * additional parameters for the Content-Type header field. 196 */ 197 public void setMultipart(Multipart multipart, Map<String, String> parameters) { 198 String mimeType = "multipart/" + multipart.getSubType(); 199 if (!parameters.containsKey("boundary")) { 200 parameters = new HashMap<String, String>(parameters); 201 parameters.put("boundary", MimeUtil.createUniqueBoundary()); 202 } 203 204 setBody(multipart, mimeType, parameters); 205 } 206 207 /** 208 * Sets the specified <code>TextBody</code> as body of this entity and the 209 * content type to "text/plain". A <code>Header</code> is 210 * created if this entity does not already have one. 211 * 212 * @param textBody 213 * the <code>TextBody</code> to set as body. 214 * @see BodyFactory#textBody(String) 215 */ 216 public void setText(TextBody textBody) { 217 setText(textBody, "plain"); 218 } 219 220 /** 221 * Sets the specified <code>TextBody</code> as body of this entity. Also 222 * sets the content type according to the specified sub-type. A 223 * <code>Header</code> is created if this entity does not already have 224 * one. 225 * 226 * @param textBody 227 * the <code>TextBody</code> to set as body. 228 * @param subtype 229 * the text subtype (e.g. "plain", "html" or 230 * "xml"). 231 * @see BodyFactory#textBody(String) 232 */ 233 public void setText(TextBody textBody, String subtype) { 234 String mimeType = "text/" + subtype; 235 236 Map<String, String> parameters = null; 237 String mimeCharset = textBody.getMimeCharset(); 238 if (mimeCharset != null && !mimeCharset.equalsIgnoreCase("us-ascii")) { 239 parameters = Collections.singletonMap("charset", mimeCharset); 240 } 241 242 setBody(textBody, mimeType, parameters); 243 } 244 245 /** 246 * Sets the body of this entity and sets the content-type to the specified 247 * value. A <code>Header</code> is created if this entity does not already 248 * have one. 249 * 250 * @param body 251 * the body. 252 * @param mimeType 253 * the MIME media type of the specified body 254 * ("type/subtype"). 255 */ 256 public void setBody(Body body, String mimeType) { 257 setBody(body, mimeType, null); 258 } 259 260 /** 261 * Sets the body of this entity and sets the content-type to the specified 262 * value. A <code>Header</code> is created if this entity does not already 263 * have one. 264 * 265 * @param body 266 * the body. 267 * @param mimeType 268 * the MIME media type of the specified body 269 * ("type/subtype"). 270 * @param parameters 271 * additional parameters for the Content-Type header field. 272 */ 273 public void setBody(Body body, String mimeType, 274 Map<String, String> parameters) { 275 setBody(body); 276 277 Header header = obtainHeader(); 278 header.setField(Fields.contentType(mimeType, parameters)); 279 } 280 281 /** 282 * Determines the MIME type of this <code>Entity</code>. The MIME type 283 * is derived by looking at the parent's Content-Type field if no 284 * Content-Type field is set for this <code>Entity</code>. 285 * 286 * @return the MIME type. 287 */ 288 public String getMimeType() { 289 ContentTypeField child = 290 (ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE); 291 ContentTypeField parent = getParent() != null 292 ? (ContentTypeField) getParent().getHeader(). 293 getField(FieldName.CONTENT_TYPE) 294 : null; 295 296 return ContentTypeField.getMimeType(child, parent); 297 } 298 299 /** 300 * Determines the MIME character set encoding of this <code>Entity</code>. 301 * 302 * @return the MIME character set encoding. 303 */ 304 public String getCharset() { 305 return ContentTypeField.getCharset( 306 (ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE)); 307 } 308 309 /** 310 * Determines the transfer encoding of this <code>Entity</code>. 311 * 312 * @return the transfer encoding. 313 */ 314 public String getContentTransferEncoding() { 315 ContentTransferEncodingField f = (ContentTransferEncodingField) 316 getHeader().getField(FieldName.CONTENT_TRANSFER_ENCODING); 317 318 return ContentTransferEncodingField.getEncoding(f); 319 } 320 321 /** 322 * Sets the transfer encoding of this <code>Entity</code> to the specified 323 * value. 324 * 325 * @param contentTransferEncoding 326 * transfer encoding to use. 327 */ 328 public void setContentTransferEncoding(String contentTransferEncoding) { 329 Header header = obtainHeader(); 330 header.setField(Fields.contentTransferEncoding(contentTransferEncoding)); 331 } 332 333 /** 334 * Return the disposition type of the content disposition of this 335 * <code>Entity</code>. 336 * 337 * @return the disposition type or <code>null</code> if no disposition 338 * type has been set. 339 */ 340 public String getDispositionType() { 341 ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); 342 if (field == null) 343 return null; 344 345 return field.getDispositionType(); 346 } 347 348 /** 349 * Sets the content disposition of this <code>Entity</code> to the 350 * specified disposition type. No filename, size or date parameters 351 * are included in the content disposition. 352 * 353 * @param dispositionType 354 * disposition type value (usually <code>inline</code> or 355 * <code>attachment</code>). 356 */ 357 public void setContentDisposition(String dispositionType) { 358 Header header = obtainHeader(); 359 header.setField(Fields.contentDisposition(dispositionType, null, -1, 360 null, null, null)); 361 } 362 363 /** 364 * Sets the content disposition of this <code>Entity</code> to the 365 * specified disposition type and filename. No size or date parameters are 366 * included in the content disposition. 367 * 368 * @param dispositionType 369 * disposition type value (usually <code>inline</code> or 370 * <code>attachment</code>). 371 * @param filename 372 * filename parameter value or <code>null</code> if the 373 * parameter should not be included. 374 */ 375 public void setContentDisposition(String dispositionType, String filename) { 376 Header header = obtainHeader(); 377 header.setField(Fields.contentDisposition(dispositionType, filename, 378 -1, null, null, null)); 379 } 380 381 /** 382 * Sets the content disposition of this <code>Entity</code> to the 383 * specified values. No date parameters are included in the content 384 * disposition. 385 * 386 * @param dispositionType 387 * disposition type value (usually <code>inline</code> or 388 * <code>attachment</code>). 389 * @param filename 390 * filename parameter value or <code>null</code> if the 391 * parameter should not be included. 392 * @param size 393 * size parameter value or <code>-1</code> if the parameter 394 * should not be included. 395 */ 396 public void setContentDisposition(String dispositionType, String filename, 397 long size) { 398 Header header = obtainHeader(); 399 header.setField(Fields.contentDisposition(dispositionType, filename, 400 size, null, null, null)); 401 } 402 403 /** 404 * Sets the content disposition of this <code>Entity</code> to the 405 * specified values. 406 * 407 * @param dispositionType 408 * disposition type value (usually <code>inline</code> or 409 * <code>attachment</code>). 410 * @param filename 411 * filename parameter value or <code>null</code> if the 412 * parameter should not be included. 413 * @param size 414 * size parameter value or <code>-1</code> if the parameter 415 * should not be included. 416 * @param creationDate 417 * creation-date parameter value or <code>null</code> if the 418 * parameter should not be included. 419 * @param modificationDate 420 * modification-date parameter value or <code>null</code> if 421 * the parameter should not be included. 422 * @param readDate 423 * read-date parameter value or <code>null</code> if the 424 * parameter should not be included. 425 */ 426 public void setContentDisposition(String dispositionType, String filename, 427 long size, Date creationDate, Date modificationDate, Date readDate) { 428 Header header = obtainHeader(); 429 header.setField(Fields.contentDisposition(dispositionType, filename, 430 size, creationDate, modificationDate, readDate)); 431 } 432 433 /** 434 * Returns the filename parameter of the content disposition of this 435 * <code>Entity</code>. 436 * 437 * @return the filename parameter of the content disposition or 438 * <code>null</code> if the filename has not been set. 439 */ 440 public String getFilename() { 441 ContentDispositionField field = obtainField(FieldName.CONTENT_DISPOSITION); 442 if (field == null) 443 return null; 444 445 return field.getFilename(); 446 } 447 448 /** 449 * Sets the filename parameter of the content disposition of this 450 * <code>Entity</code> to the specified value. If this entity does not 451 * have a content disposition header field a new one with disposition type 452 * <code>attachment</code> is created. 453 * 454 * @param filename 455 * filename parameter value or <code>null</code> if the 456 * parameter should be removed. 457 */ 458 public void setFilename(String filename) { 459 Header header = obtainHeader(); 460 ContentDispositionField field = (ContentDispositionField) header 461 .getField(FieldName.CONTENT_DISPOSITION); 462 if (field == null) { 463 if (filename != null) { 464 header.setField(Fields.contentDisposition( 465 ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT, 466 filename, -1, null, null, null)); 467 } 468 } else { 469 String dispositionType = field.getDispositionType(); 470 Map<String, String> parameters = new HashMap<String, String>(field 471 .getParameters()); 472 if (filename == null) { 473 parameters.remove(ContentDispositionField.PARAM_FILENAME); 474 } else { 475 parameters 476 .put(ContentDispositionField.PARAM_FILENAME, filename); 477 } 478 header.setField(Fields.contentDisposition(dispositionType, 479 parameters)); 480 } 481 } 482 483 /** 484 * Determines if the MIME type of this <code>Entity</code> matches the 485 * given one. MIME types are case-insensitive. 486 * 487 * @param type the MIME type to match against. 488 * @return <code>true</code> on match, <code>false</code> otherwise. 489 */ 490 public boolean isMimeType(String type) { 491 return getMimeType().equalsIgnoreCase(type); 492 } 493 494 /** 495 * Determines if the MIME type of this <code>Entity</code> is 496 * <code>multipart/*</code>. Since multipart-entities must have 497 * a boundary parameter in the <code>Content-Type</code> field this 498 * method returns <code>false</code> if no boundary exists. 499 * 500 * @return <code>true</code> on match, <code>false</code> otherwise. 501 */ 502 public boolean isMultipart() { 503 ContentTypeField f = 504 (ContentTypeField) getHeader().getField(FieldName.CONTENT_TYPE); 505 return f != null && f.getBoundary() != null 506 && getMimeType().startsWith(ContentTypeField.TYPE_MULTIPART_PREFIX); 507 } 508 509 /** 510 * Disposes of the body of this entity. Note that the dispose call does not 511 * get forwarded to the parent entity of this Entity. 512 * 513 * Subclasses that need to free resources should override this method and 514 * invoke super.dispose(). 515 * 516 * @see org.apache.james.mime4j.message.Disposable#dispose() 517 */ 518 public void dispose() { 519 if (body != null) { 520 body.dispose(); 521 } 522 } 523 524 /** 525 * Obtains the header of this entity. Creates and sets a new header if this 526 * entity's header is currently <code>null</code>. 527 * 528 * @return the header of this entity; never <code>null</code>. 529 */ 530 Header obtainHeader() { 531 if (header == null) { 532 header = new Header(); 533 } 534 return header; 535 } 536 537 /** 538 * Obtains the header field with the specified name. 539 * 540 * @param <F> 541 * concrete field type. 542 * @param fieldName 543 * name of the field to retrieve. 544 * @return the header field or <code>null</code> if this entity has no 545 * header or the header contains no such field. 546 */ 547 <F extends Field> F obtainField(String fieldName) { 548 Header header = getHeader(); 549 if (header == null) 550 return null; 551 552 @SuppressWarnings("unchecked") 553 F field = (F) header.getField(fieldName); 554 return field; 555 } 556 557 }