1/*
2 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.xml.internal.org.jvnet.mimepull;
27
28import java.io.Closeable;
29import java.io.File;
30import java.io.InputStream;
31import java.nio.ByteBuffer;
32import java.util.List;
33import java.util.logging.Level;
34import java.util.logging.Logger;
35
36/**
37 * Represents an attachment part in a MIME message. MIME message parsing is done
38 * lazily using a pull parser, so the part may not have all the data. {@link #read}
39 * and {@link #readOnce} may trigger the actual parsing the message. In fact,
40 * parsing of an attachment part may be triggered by calling {@link #read} methods
41 * on some other attachment parts. All this happens behind the scenes so the
42 * application developer need not worry about these details.
43 *
44 * @author Jitendra Kotamraju, Martin Grebac
45 */
46public class MIMEPart implements Closeable {
47
48    private static final Logger LOGGER = Logger.getLogger(MIMEPart.class.getName());
49
50    private volatile boolean closed;
51    private volatile InternetHeaders headers;
52    private volatile String contentId;
53    private String contentType;
54    private String contentTransferEncoding;
55
56    volatile boolean parsed;    // part is parsed or not
57    final MIMEMessage msg;
58    private final DataHead dataHead;
59
60    private final Object lock = new Object();
61
62    MIMEPart(MIMEMessage msg) {
63        this.msg = msg;
64        this.dataHead = new DataHead(this);
65    }
66
67    MIMEPart(MIMEMessage msg, String contentId) {
68        this(msg);
69        this.contentId = contentId;
70    }
71
72    /**
73     * Can get the attachment part's content multiple times. That means
74     * the full content needs to be there in memory or on the file system.
75     * Calling this method would trigger parsing for the part's data. So
76     * do not call this unless it is required(otherwise, just wrap MIMEPart
77     * into a object that returns InputStream for e.g DataHandler)
78     *
79     * @return data for the part's content
80     */
81    public InputStream read() {
82        InputStream is = null;
83        try {
84            is = MimeUtility.decode(dataHead.read(), contentTransferEncoding);
85        } catch (DecodingException ex) { //ignore
86            if (LOGGER.isLoggable(Level.WARNING)) {
87                LOGGER.log(Level.WARNING, null, ex);
88            }
89        }
90        return is;
91    }
92
93    /**
94     * Cleans up any resources that are held by this part (for e.g. deletes
95     * the temp file that is used to serve this part's content). After
96     * calling this, one shouldn't call {@link #read()} or {@link #readOnce()}
97     */
98    @Override
99    public void close() {
100        if (!closed) {
101            synchronized (lock) {
102                if (!closed) {
103                    dataHead.close();
104                    closed = true;
105                }
106            }
107        }
108    }
109
110    /**
111     * Can get the attachment part's content only once. The content
112     * will be lost after the method. Content data is not be stored
113     * on the file system or is not kept in the memory for the
114     * following case:
115     *   - Attachement parts contents are accessed sequentially
116     *
117     * In general, take advantage of this when the data is used only
118     * once.
119     *
120     * @return data for the part's content
121     */
122    public InputStream readOnce() {
123        InputStream is = null;
124        try {
125            is = MimeUtility.decode(dataHead.readOnce(), contentTransferEncoding);
126        } catch (DecodingException ex) { //ignore
127            if (LOGGER.isLoggable(Level.WARNING)) {
128                LOGGER.log(Level.WARNING, null, ex);
129            }
130        }
131        return is;
132    }
133
134    public void moveTo(File f) {
135        dataHead.moveTo(f);
136    }
137
138    /**
139     * Returns Content-ID MIME header for this attachment part
140     *
141     * @return Content-ID of the part
142     */
143    public String getContentId() {
144        if (contentId == null) {
145            getHeaders();
146        }
147        return contentId;
148    }
149
150    /**
151     * Returns Content-Transfer-Encoding MIME header for this attachment part
152     *
153     * @return Content-Transfer-Encoding of the part
154     */
155    public String getContentTransferEncoding() {
156        if (contentTransferEncoding == null) {
157            getHeaders();
158        }
159        return contentTransferEncoding;
160    }
161
162    /**
163     * Returns Content-Type MIME header for this attachment part
164     *
165     * @return Content-Type of the part
166     */
167    public String getContentType() {
168        if (contentType == null) {
169            getHeaders();
170        }
171        return contentType;
172    }
173
174    private void getHeaders() {
175        // Trigger parsing for the part headers
176        while(headers == null) {
177            if (!msg.makeProgress()) {
178                if (headers == null) {
179                    throw new IllegalStateException("Internal Error. Didn't get Headers even after complete parsing.");
180                }
181            }
182        }
183    }
184
185    /**
186     * Return all the values for the specified header.
187     * Returns <code>null</code> if no headers with the
188     * specified name exist.
189     *
190     * @param   name header name
191     * @return  list of header values, or null if none
192     */
193    public List<String> getHeader(String name) {
194        getHeaders();
195        assert headers != null;
196        return headers.getHeader(name);
197    }
198
199    /**
200     * Return all the headers
201     *
202     * @return list of Header objects
203     */
204    public List<? extends Header> getAllHeaders() {
205        getHeaders();
206        assert headers != null;
207        return headers.getAllHeaders();
208    }
209
210    /**
211     * Callback to set headers
212     *
213     * @param headers MIME headers for the part
214     */
215    void setHeaders(InternetHeaders headers) {
216        this.headers = headers;
217        List<String> ct = getHeader("Content-Type");
218        this.contentType = (ct == null) ? "application/octet-stream" : ct.get(0);
219        List<String> cte = getHeader("Content-Transfer-Encoding");
220        this.contentTransferEncoding = (cte == null) ? "binary" : cte.get(0);
221    }
222
223    /**
224     * Callback to notify that there is a partial content for the part
225     *
226     * @param buf content data for the part
227     */
228    void addBody(ByteBuffer buf) {
229        dataHead.addBody(buf);
230    }
231
232    /**
233     * Callback to indicate that parsing is done for this part
234     * (no more update events for this part)
235     */
236    void doneParsing() {
237        parsed = true;
238        dataHead.doneParsing();
239    }
240
241    /**
242     * Callback to set Content-ID for this part
243     * @param cid Content-ID of the part
244     */
245    void setContentId(String cid) {
246        this.contentId = cid;
247    }
248
249    /**
250     * Callback to set Content-Transfer-Encoding for this part
251     * @param cte Content-Transfer-Encoding of the part
252     */
253    void setContentTransferEncoding(String cte) {
254        this.contentTransferEncoding = cte;
255    }
256
257    /**
258     * Return {@code true} if this part has already been closed, {@code false} otherwise.
259     *
260     * @return {@code true} if this part has already been closed, {@code false} otherwise.
261     */
262    public boolean isClosed() {
263        return closed;
264    }
265
266    @Override
267    public String toString() {
268        return "Part="+contentId+":"+contentTransferEncoding;
269    }
270
271}
272