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