/* Copyright (c) 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.gdata.data.media; import com.google.gdata.util.common.base.Preconditions; import com.google.gdata.client.media.MediaService; import com.google.gdata.data.Entry; import com.google.gdata.data.ExtensionProfile; import com.google.gdata.data.IAtom; import com.google.gdata.data.ParseSource; import com.google.gdata.util.ContentType; import com.google.gdata.util.ParseException; import com.google.gdata.util.ServiceException; import com.google.gdata.wireformats.AltFormat; import com.google.gdata.wireformats.AltRegistry; import com.google.gdata.wireformats.input.ForwardingInputProperties; import com.google.gdata.wireformats.input.InputProperties; import com.google.gdata.wireformats.input.InputParser; import com.google.gdata.wireformats.input.InputPropertiesBuilder; import com.google.gdata.wireformats.output.ForwardingOutputProperties; import com.google.gdata.wireformats.output.OutputGenerator; import com.google.gdata.wireformats.output.OutputProperties; import com.google.gdata.wireformats.output.OutputPropertiesBuilder; import java.awt.datatransfer.DataFlavor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import javax.activation.DataContentHandler; import javax.activation.DataSource; /** * The GDataContentHandler class implements the {@link DataContentHandler} * interface of the <a href="http://java.sun.com/products/javabeans/jaf/ "> * JavaBeans Activation Framework</a> to enable the parsing and generation * of Atom feed and entry XML from MIME media data. This data content * handler is capable of generating MIME media output in Atom, RSS, and * JSON formats, as well as parsing content in Atom format. * <p> * The implementation includes support for customized types driven by * <a href="http://code.google.com/apis/gdata/common-elements.html"> * GData Kinds</a>, where the type of object returned might be triggered * by the GData kind category tags included within the content. * <p> * The current implementation does not include DataFlavor transfer support, * only mapping from raw MIME data content to object model (and vice versa). * * */ public class GDataContentHandler implements DataContentHandler { /** * Defines the default {@link InputProperties} that are used for content * parsing. These properties will use the default media registry, expect * Atom content, produces generic {@link Entry} instances, and use an * empty extension profile. */ private static final InputProperties DEFAULT_INPUT_PROPERTIES = new InputPropertiesBuilder() .setAltRegistry(MediaService.getDefaultAltRegistry()) .setContentType(ContentType.ATOM) .setExpectType(Entry.class) .setExtensionProfile(new ExtensionProfile()) .build(); /** * Thread local storage that defines the input properties that should be * used when parsing GData content */ private static final ThreadLocal<InputProperties> threadInputProperties = new ThreadLocal<InputProperties>() { @Override protected InputProperties initialValue() { return DEFAULT_INPUT_PROPERTIES; } }; /** * Defines the default {@link OutputProperties} that are used for content * parsing. These properties will use a default media registry and an * empty extension profile. */ private static final OutputProperties DEFAULT_OUTPUT_PROPERTIES = new OutputPropertiesBuilder() .setAltRegistry(MediaService.getDefaultAltRegistry()) .setExtensionProfile(new ExtensionProfile()) .build(); /** * Thread local storage that defines the output properties that should be * used when generating GData content */ private static final ThreadLocal<OutputProperties> threadOutputProperties = new ThreadLocal<OutputProperties>() { @Override protected OutputProperties initialValue() { return DEFAULT_OUTPUT_PROPERTIES; } }; /** * Sets the input properties for the current {@link java.lang.Thread} and * returns any existing input properties that have been set (so they can be * restored later). */ public static InputProperties setThreadInputProperties( InputProperties inputProperties) { Preconditions.checkNotNull(inputProperties, "inputProperties"); InputProperties currentProperties = getThreadInputProperties(); threadInputProperties.set(inputProperties); return currentProperties; } /** * Returns the input properties for the current {@link java.lang.Thread}. */ public static InputProperties getThreadInputProperties() { return threadInputProperties.get(); } /** * Sets the output properties for the current {@link java.lang.Thread} and * returns any existing input properties that have been set (so they can be * restored later). */ public static OutputProperties setThreadOutputProperties( OutputProperties outputProperties) { Preconditions.checkNotNull(outputProperties, "outputProperties"); OutputProperties currentProperties = getThreadOutputProperties(); threadOutputProperties.set(outputProperties); return currentProperties; } /** * Returns the output properties for the current {@link java.lang.Thread}. */ public static OutputProperties getThreadOutputProperties() { return threadOutputProperties.get(); } public DataFlavor[] getTransferDataFlavors() { throw new UnsupportedOperationException("No DataFlavor support"); } public Object getTransferData(DataFlavor df, DataSource ds) { throw new UnsupportedOperationException("No DataFlavor support"); } @SuppressWarnings("unchecked") private <T> Object parseAtom(InputParser<?> parser, InputStream inputStream, final ContentType contentType, InputProperties inputProperties, Class<T> resultClass) throws IOException, ServiceException { Preconditions.checkArgument( parser.getResultType().isAssignableFrom(IAtom.class), "Parser does not handle atom content"); return ((InputParser<T>) parser).parse( new ParseSource(inputStream), new ForwardingInputProperties(inputProperties) { @Override public ContentType getContentType() { return contentType; } }, resultClass); } @SuppressWarnings("unchecked") public Object getContent(DataSource ds) throws IOException { // Get the input properties to use when parsing content InputProperties inputProperties = getThreadInputProperties(); // Find the parser to handle the input content type ContentType contentType = new ContentType(ds.getContentType()); AltRegistry altRegistry = inputProperties.getAltRegistry(); AltFormat altFormat = altRegistry.lookupType(contentType); InputParser<?> parser = altRegistry.getParser(altFormat); if (parser == null) { throw new IOException("Invalid multipart content: " + contentType); } try { return parseAtom(parser, ds.getInputStream(), contentType, inputProperties, inputProperties.getRootType()); } catch (ServiceException se) { IOException ioe = new IOException("Error parsing content"); ioe.initCause(se); throw ioe; } } private void generateAtom(OutputGenerator<?> generator, OutputStream outputStream, OutputProperties outputProperties, Object source) throws IOException { // Make sure the parser will accept any IAtom type Preconditions.checkArgument( generator.getSourceType().isAssignableFrom(IAtom.class), "Generator does not handle atom content"); Preconditions.checkArgument(source instanceof IAtom, "Source object must be Atom content"); IAtom atomSource = (IAtom) source; @SuppressWarnings("unchecked") // safe given above check OutputGenerator<IAtom> atomGenerator = (OutputGenerator<IAtom>) generator; atomGenerator.generate(outputStream, outputProperties, atomSource); } public void writeTo(Object obj, String mimeType, OutputStream os) throws IOException { Preconditions.checkNotNull(obj, "obj"); // Get the output properties to use when generating content OutputProperties outputProperties = getThreadOutputProperties(); AltRegistry altRegistry = outputProperties.getAltRegistry(); ContentType contentType = new ContentType(mimeType); final AltFormat altFormat = altRegistry.lookupType(contentType); OutputGenerator<?> generator = altRegistry.getGenerator(altFormat); if (generator == null) { throw new IllegalStateException("Unable to generate media: " + contentType); } generateAtom(generator, os, new ForwardingOutputProperties(outputProperties) { @Override public ContentType getContentType() { return altFormat.getContentType(); } }, obj); } }