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 javax.activation;
021
022 import java.awt.datatransfer.DataFlavor;
023 import java.awt.datatransfer.Transferable;
024 import java.awt.datatransfer.UnsupportedFlavorException;
025 import java.io.IOException;
026 import java.io.InputStream;
027 import java.io.OutputStream;
028 import java.io.PipedInputStream;
029 import java.io.PipedOutputStream;
030 import java.net.URL;
031
032 /**
033 * @version $Rev: 467742 $ $Date: 2006-10-25 15:30:38 -0400 (Wed, 25 Oct 2006) $
034 */
035 public class DataHandler implements Transferable {
036 private final DataSource ds;
037 private final DataFlavor flavor;
038
039 private CommandMap commandMap;
040 private DataContentHandler dch;
041
042 public DataHandler(DataSource ds) {
043 this.ds = ds;
044 this.flavor = new ActivationDataFlavor(ds.getContentType(), null);
045 }
046
047 public DataHandler(Object data, String type) {
048 this.ds = new ObjectDataSource(data, type);
049 this.flavor = new ActivationDataFlavor(data.getClass(), null);
050 }
051
052 public DataHandler(URL url) {
053 this.ds = new URLDataSource(url);
054 this.flavor = new ActivationDataFlavor(ds.getContentType(), null);
055 }
056
057 public DataSource getDataSource() {
058 return ds;
059 }
060
061 public String getName() {
062 return ds.getName();
063 }
064
065 public String getContentType() {
066 return ds.getContentType();
067 }
068
069 public InputStream getInputStream() throws IOException {
070 return ds.getInputStream();
071 }
072
073 public void writeTo(OutputStream os) throws IOException {
074 if (ds instanceof ObjectDataSource) {
075 ObjectDataSource ods = (ObjectDataSource) ds;
076 DataContentHandler dch = getDataContentHandler();
077 if (dch == null) {
078 throw new UnsupportedDataTypeException(ods.mimeType);
079 }
080 dch.writeTo(ods.data, ods.mimeType, os);
081 } else {
082 byte[] buffer = new byte[1024];
083 InputStream is = getInputStream();
084 try {
085 int count;
086 while ((count = is.read(buffer)) != -1) {
087 os.write(buffer, 0, count);
088 }
089 } finally {
090 is.close();
091 }
092 }
093 }
094
095 public OutputStream getOutputStream() throws IOException {
096 return ds.getOutputStream();
097 }
098
099 public synchronized DataFlavor[] getTransferDataFlavors() {
100 return getDataContentHandler().getTransferDataFlavors();
101 }
102
103 public boolean isDataFlavorSupported(DataFlavor flavor) {
104 DataFlavor[] flavors = getTransferDataFlavors();
105 for (int i = 0; i < flavors.length; i++) {
106 DataFlavor dataFlavor = flavors[i];
107 if (dataFlavor.equals(flavor)) {
108 return true;
109 }
110 }
111 return false;
112 }
113
114 public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
115 DataContentHandler dch = getDataContentHandler();
116 if (dch != null) {
117 return dch.getTransferData(flavor, ds);
118 } else if (this.flavor.match(flavor)) {
119 if (ds instanceof ObjectDataSource) {
120 return ((ObjectDataSource) ds).data;
121 } else {
122 return ds.getInputStream();
123 }
124 } else {
125 throw new UnsupportedFlavorException(flavor);
126 }
127 }
128
129 public CommandInfo[] getPreferredCommands() {
130 return getCommandMap().getPreferredCommands(ds.getContentType());
131 }
132
133 public CommandInfo[] getAllCommands() {
134 return getCommandMap().getAllCommands(ds.getContentType());
135 }
136
137 public CommandInfo getCommand(String cmdName) {
138 return getCommandMap().getCommand(ds.getContentType(), cmdName);
139 }
140
141 public Object getContent() throws IOException {
142 if (ds instanceof ObjectDataSource) {
143 return ((ObjectDataSource) ds).data;
144 } else {
145 DataContentHandler dch = getDataContentHandler();
146 if (dch != null) {
147 return dch.getContent(ds);
148 } else {
149 return ds.getInputStream();
150 }
151 }
152 }
153
154 public Object getBean(CommandInfo cmdinfo) {
155 try {
156 return cmdinfo.getCommandObject(this, this.getClass().getClassLoader());
157 } catch (IOException e) {
158 return null;
159 } catch (ClassNotFoundException e) {
160 return null;
161 }
162 }
163
164 /**
165 * A local implementation of DataSouce used to wrap an Object and mime-type.
166 */
167 private class ObjectDataSource implements DataSource {
168 private final Object data;
169 private final String mimeType;
170
171 public ObjectDataSource(Object data, String mimeType) {
172 this.data = data;
173 this.mimeType = mimeType;
174 }
175
176 public String getName() {
177 return null;
178 }
179
180 public String getContentType() {
181 return mimeType;
182 }
183
184 public InputStream getInputStream() throws IOException {
185 final DataContentHandler dch = getDataContentHandler();
186 if (dch == null) {
187 throw new UnsupportedDataTypeException(mimeType);
188 }
189 final PipedInputStream is = new PipedInputStream();
190 final PipedOutputStream os = new PipedOutputStream(is);
191 Thread thread = new Thread("DataHandler Pipe Pump") {
192 public void run() {
193 try {
194 try {
195 dch.writeTo(data, mimeType, os);
196 } finally {
197 os.close();
198 }
199 } catch (IOException e) {
200 // ignore, per spec - doh!
201 }
202 }
203 };
204 thread.start();
205 return is;
206 }
207
208 public OutputStream getOutputStream() throws IOException {
209 return null;
210 }
211 }
212
213 public synchronized void setCommandMap(CommandMap commandMap) {
214 this.commandMap = commandMap;
215 this.dch = null;
216 }
217
218 private synchronized CommandMap getCommandMap() {
219 return commandMap != null ? commandMap : CommandMap.getDefaultCommandMap();
220 }
221
222 /**
223 * Search for a DataContentHandler for our mime type.
224 * The search is performed by first checking if a global factory has been set using
225 * {@link #setDataContentHandlerFactory(DataContentHandlerFactory)};
226 * if found then it is called to attempt to create a handler.
227 * If this attempt fails, we then call the command map set using {@link #setCommandMap(CommandMap)}
228 * (or if that has not been set, the default map returned by {@link CommandMap#getDefaultCommandMap()})
229 * to create the handler.
230 *
231 * The resulting handler is cached until the global factory is changed.
232 *
233 * @return
234 */
235 private synchronized DataContentHandler getDataContentHandler() {
236 DataContentHandlerFactory localFactory;
237 synchronized (DataHandler.class) {
238 if (factory != originalFactory) {
239 // setDCHF was called - clear our cached copy of the DCH and DCHF
240 dch = null;
241 originalFactory = factory;
242 }
243 localFactory = originalFactory;
244 }
245 if (dch == null) {
246 // get the main mime-type portion of the content.
247 String mimeType = getMimeType(ds.getContentType());
248 if (localFactory != null) {
249 dch = localFactory.createDataContentHandler(mimeType);
250 }
251 if (dch == null) {
252 if (commandMap != null) {
253 dch = commandMap.createDataContentHandler(mimeType);
254 } else {
255 dch = CommandMap.getDefaultCommandMap().createDataContentHandler(mimeType);
256 }
257 }
258 }
259 return dch;
260 }
261
262 /**
263 * Retrieve the base MIME type from a content type. This parses
264 * the type into its base components, stripping off any parameter
265 * information.
266 *
267 * @param contentType
268 * The content type string.
269 *
270 * @return The MIME type identifier portion of the content type.
271 */
272 private String getMimeType(String contentType) {
273 try {
274 MimeType mimeType = new MimeType(contentType);
275 return mimeType.getBaseType();
276 } catch (MimeTypeParseException e) {
277 }
278 return contentType;
279 }
280
281 /**
282 * This is used to check if the DataContentHandlerFactory has been changed.
283 * This is not specified behaviour but this check is required to make this work like the RI.
284 */
285 private DataContentHandlerFactory originalFactory;
286
287 {
288 synchronized (DataHandler.class) {
289 originalFactory = factory;
290 }
291 }
292
293 private static DataContentHandlerFactory factory;
294
295 /**
296 * Set the DataContentHandlerFactory to use.
297 * If this method has already been called then an Error is raised.
298 *
299 * @param newFactory the new factory
300 * @throws SecurityException if the caller does not have "SetFactory" RuntimePermission
301 */
302 public static synchronized void setDataContentHandlerFactory(DataContentHandlerFactory newFactory) {
303 if (factory != null) {
304 throw new Error("javax.activation.DataHandler.setDataContentHandlerFactory has already been defined");
305 }
306 SecurityManager sm = System.getSecurityManager();
307 if (sm != null) {
308 sm.checkSetFactory();
309 }
310 factory = newFactory;
311 }
312 }