| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| package org.eclipse.persistence.jaxb; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Reader; |
| import java.lang.reflect.Type; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import jakarta.xml.bind.JAXBElement; |
| import jakarta.xml.bind.JAXBException; |
| import jakarta.xml.bind.Unmarshaller; |
| import jakarta.xml.bind.helpers.DefaultValidationEventHandler; |
| import javax.xml.stream.XMLEventReader; |
| import javax.xml.stream.XMLStreamReader; |
| import javax.xml.transform.Source; |
| import javax.xml.transform.dom.DOMSource; |
| import javax.xml.transform.stream.StreamSource; |
| |
| import org.eclipse.persistence.jaxb.JAXBContext.JAXBContextInput; |
| import org.eclipse.persistence.jaxb.JAXBContext.ContextPathInput; |
| import org.eclipse.persistence.jaxb.JAXBContext.TypeMappingInfoInput; |
| import org.eclipse.persistence.jaxb.compiler.CompilerHelper; |
| import org.eclipse.persistence.jaxb.compiler.XMLProcessor; |
| import org.eclipse.persistence.jaxb.metadata.MetadataSource; |
| import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings; |
| import org.eclipse.persistence.oxm.MediaType; |
| import org.w3c.dom.Node; |
| import org.xml.sax.InputSource; |
| |
| /** |
| * <p> |
| * <b>Purpose:</b>An EclipseLink specific JAXBContextFactory. This class can be specified in a |
| * jaxb.properties file to make use of EclipseLink's JAXB 2.1 implementation. |
| * <p> |
| * <b>Responsibilities:</b> |
| * <ul> |
| * <li>Create a JAXBContext from an array of Classes and a Properties object</li> |
| * <li>Create a JAXBContext from a context path and a classloader</li> |
| * </ul> |
| * <p> |
| * This class is the entry point into in EclipseLink's JAXB 2.1 Runtime. It provides the required |
| * factory methods and is invoked by jakarta.xml.bind.JAXBContext.newInstance() to create new |
| * instances of JAXBContext. When creating a JAXBContext from a contextPath, the list of classes is |
| * derived either from an ObjectFactory class (schema-to-java) or a jaxb.index file |
| * (java-to-schema). |
| * |
| * @author mmacivor |
| * @see jakarta.xml.bind.JAXBContext |
| * @see org.eclipse.persistence.jaxb.JAXBContext |
| * @see org.eclipse.persistence.jaxb.compiler.Generator |
| */ |
| public class JAXBContextFactory { |
| |
| /** |
| * @deprecated As of release 2.4, replaced by JAXBContextProperties.OXM_METADATA_SOURCE |
| * @see org.eclipse.persistence.jaxb.JAXBContextProperties#OXM_METADATA_SOURCE |
| */ |
| @Deprecated |
| public static final String ECLIPSELINK_OXM_XML_KEY = "eclipselink-oxm-xml"; |
| /** |
| * @deprecated As of release 2.4, replaced by JAXBContextProperties.DEFAULT_TARGET_NAMESPACE |
| * @see org.eclipse.persistence.jaxb.JAXBContextProperties#DEFAULT_TARGET_NAMESPACE |
| */ |
| @Deprecated |
| public static final String DEFAULT_TARGET_NAMESPACE_KEY = "defaultTargetNamespace"; |
| /** |
| * @deprecated As of release 2.4, replaced by JAXBContextProperties.ANNOTATION_HELPER |
| * @see org.eclipse.persistence.jaxb.JAXBContextProperties#ANNOTATION_HELPER |
| */ |
| @Deprecated |
| public static final String ANNOTATION_HELPER_KEY = "annotationHelper"; |
| public static final String PKG_SEPARATOR = "."; |
| |
| /** |
| * Create a JAXBContext on the array of Class objects. The JAXBContext will |
| * also be aware of classes reachable from the classes in the array. |
| */ |
| public static jakarta.xml.bind.JAXBContext createContext(Class<?>[] classesToBeBound, Map<String, Object> properties) throws JAXBException { |
| ClassLoader loader = null; |
| if (classesToBeBound.length > 0) { |
| loader = classesToBeBound[0].getClassLoader(); |
| } |
| return createContext(classesToBeBound, properties, loader); |
| } |
| |
| /** |
| * Create a JAXBContext on the array of Class objects. The JAXBContext will |
| * also be aware of classes reachable from the classes in the array. |
| */ |
| public static jakarta.xml.bind.JAXBContext createContext(Class<?>[] classesToBeBound, Map<String, Object> properties, ClassLoader classLoader) throws JAXBException { |
| Type[] types = new Type[classesToBeBound.length]; |
| System.arraycopy(classesToBeBound, 0, types, 0, classesToBeBound.length); |
| return createContext(types, properties, classLoader); |
| } |
| |
| /** |
| * Create a JAXBContext on context path. The JAXBContext will |
| * also be aware of classes reachable from the classes on the context path. |
| */ |
| public static jakarta.xml.bind.JAXBContext createContext(String contextPath, ClassLoader classLoader) throws JAXBException { |
| return createContext(contextPath, classLoader, null); |
| } |
| |
| /** |
| * Create a JAXBContext on context path. The JAXBContext will |
| * also be aware of classes reachable from the classes on the context path. |
| */ |
| public static jakarta.xml.bind.JAXBContext createContext(String contextPath, ClassLoader classLoader, Map<String, Object> properties) throws JAXBException { |
| JAXBContextInput contextInput = new ContextPathInput(contextPath, properties, classLoader); |
| JAXBContext context = new JAXBContext(contextInput); |
| if (context.isRefreshable()) { |
| context.postInitialize(); |
| } |
| return context; |
| } |
| |
| /** |
| * Create a JAXBContext on the array of Type objects. The JAXBContext will |
| * also be aware of classes reachable from the types in the array. The |
| * preferred means of creating a Type aware JAXBContext is to create the |
| * JAXBContext with an array of TypeMappingInfo objects. |
| */ |
| public static jakarta.xml.bind.JAXBContext createContext(Type[] typesToBeBound, Map<String, Object> properties, ClassLoader classLoader) throws JAXBException { |
| Map<Type, TypeMappingInfo> typeToTypeMappingInfo = new HashMap<Type, TypeMappingInfo>(); |
| TypeMappingInfo[] typeMappingInfos = new TypeMappingInfo[typesToBeBound.length]; |
| for(int i = 0; i < typesToBeBound.length; i++) { |
| TypeMappingInfo tmi = new TypeMappingInfo(); |
| tmi.setType(typesToBeBound[i]); |
| typeToTypeMappingInfo.put(typesToBeBound[i], tmi); |
| typeMappingInfos[i] = tmi; |
| } |
| |
| JAXBContext context = (JAXBContext)createContext(typeMappingInfos, properties, classLoader); |
| context.setTypeToTypeMappingInfo(typeToTypeMappingInfo); |
| |
| return context; |
| } |
| |
| /** |
| * Create a JAXBContext on the array of TypeMappingInfo objects. The |
| * JAXBContext will also be aware of classes reachable from the types in the |
| * array. This is the preferred means of creating a Type aware JAXBContext. |
| */ |
| public static jakarta.xml.bind.JAXBContext createContext(TypeMappingInfo[] typesToBeBound, Map<String, Object> properties, ClassLoader classLoader) throws JAXBException { |
| JAXBContextInput contextInput = new TypeMappingInfoInput(typesToBeBound, properties, classLoader); |
| JAXBContext context = new JAXBContext(contextInput); |
| if (context.isRefreshable()) { |
| context.postInitialize(); |
| } |
| return context; |
| } |
| |
| /** |
| * <p>Convenience method for processing a properties map and creating a map of |
| * package names to XmlBindings instances.</p> |
| * |
| * <p>It is assumed that the given map's key will be JAXBContextProperties.OXM_METADATA_SOURCE, |
| * and the value will be:</p> |
| * <pre> |
| * 1) {@literal Map<String, Object>} |
| * - Object is one of those listed in 3) below |
| * 2) {@literal List<Object>} |
| * - Object is one of those listed in 3) below |
| * - Bindings file must contain package-name attribute on |
| * xml-bindings element |
| * 3) One of: |
| * - java.io.File |
| * - java.io.InputStream |
| * - java.io.Reader |
| * - java.lang.String |
| * - java.net.URL |
| * - javax.xml.stream.XMLEventReader |
| * - javax.xml.stream.XMLStreamReader |
| * - javax.xml.transform.Source |
| * - org.eclipse.persistence.jaxb.metadata.MetadataSource |
| * - org.w3c.dom.Node |
| * - org.xml.sax.InputSource |
| * |
| * - Bindings file must contain package-name attribute on |
| * xml-bindings element |
| * </pre> |
| */ |
| public static Map<String, XmlBindings> getXmlBindingsFromProperties(Map<String, Object> properties, ClassLoader classLoader) { |
| Map<String, List<XmlBindings>> bindings = new HashMap<String, List<XmlBindings>>(); |
| Object value = null; |
| if (properties != null) { |
| if ((value = properties.get(JAXBContextProperties.OXM_METADATA_SOURCE)) == null) { |
| // try looking up the 'old' metadata source key |
| value = properties.get(ECLIPSELINK_OXM_XML_KEY); |
| } |
| } |
| if (value != null) { |
| // handle Map<String, Object> |
| if (value instanceof Map) { |
| Map<String, Object> metadataFiles = null; |
| try { |
| metadataFiles = (Map<String, Object>) value; |
| } catch (ClassCastException x) { |
| throw org.eclipse.persistence.exceptions.JAXBException.incorrectValueParameterTypeForOxmXmlKey(); |
| } |
| for(Entry<String, Object> entry : metadataFiles.entrySet()) { |
| String key = null; |
| List<XmlBindings> xmlBindings = new ArrayList<XmlBindings>(); |
| try { |
| key = entry.getKey(); |
| if (key == null) { |
| throw org.eclipse.persistence.exceptions.JAXBException.nullMapKey(); |
| } |
| } catch (ClassCastException cce) { |
| throw org.eclipse.persistence.exceptions.JAXBException.incorrectKeyParameterType(); |
| } |
| Object metadataSource = entry.getValue(); |
| if (metadataSource == null) { |
| throw org.eclipse.persistence.exceptions.JAXBException.nullMetadataSource(key); |
| } |
| if(metadataSource instanceof List) { |
| for(Object next: (List)metadataSource) { |
| XmlBindings binding = getXmlBindings(next, classLoader, properties); |
| if(binding != null) { |
| xmlBindings.add(binding); |
| } |
| } |
| } else { |
| XmlBindings binding = getXmlBindings(metadataSource, classLoader, properties); |
| if(binding != null) { |
| xmlBindings.add(binding); |
| } |
| } |
| if (xmlBindings != null) { |
| bindings.put(key, xmlBindings); |
| } |
| } |
| // handle List<Object> |
| } else if (value instanceof List) { |
| for (Object metadataSource : (List) value) { |
| if (metadataSource == null) { |
| throw org.eclipse.persistence.exceptions.JAXBException.nullMetadataSource(); |
| } |
| bindings = processBindingFile(bindings, metadataSource, classLoader, properties); |
| } |
| // handle Object |
| } else { |
| bindings = processBindingFile(bindings, value, classLoader, properties); |
| } |
| } |
| Map<String, XmlBindings> mergedBindings = new HashMap<String, XmlBindings>(bindings.size()); |
| for(Entry<String, List<XmlBindings>> next:bindings.entrySet()) { |
| mergedBindings.put(next.getKey(), XMLProcessor.mergeXmlBindings(next.getValue())); |
| } |
| return mergedBindings; |
| } |
| |
| /** |
| * Processing a bindings file and add it to a given Map of package name to binding |
| * files. |
| * |
| * @param originalBindings Map of bindings to be updated |
| * @param bindingHandle handle to bindings file |
| */ |
| private static Map<String, List<XmlBindings>> processBindingFile(Map<String, List<XmlBindings>> originalBindings, Object bindingHandle, ClassLoader classLoader, Map<String, Object> properties) { |
| XmlBindings binding = getXmlBindings(bindingHandle, classLoader, properties); |
| if (binding != null) { |
| String key = binding.getPackageName(); |
| if (key.equals(XMLProcessor.DEFAULT)) { |
| throw org.eclipse.persistence.exceptions.JAXBException.packageNotSetForBindingException(); |
| } |
| List<XmlBindings> existingBindings = originalBindings.get(key); |
| if(existingBindings != null) { |
| existingBindings.add(binding); |
| } else { |
| existingBindings = new ArrayList<XmlBindings>(); |
| existingBindings.add(binding); |
| originalBindings.put(key, existingBindings); |
| } |
| } |
| return originalBindings; |
| } |
| |
| /** |
| * Convenience method for creating an XmlBindings object based on a given Object. The method |
| * will load the eclipselink metadata model and unmarshal the Object. This assumes that the |
| * Object represents the eclipselink-oxm.xml metadata file to be unmarshalled. |
| * |
| * @param metadata assumed to be one of: File, InputSource, InputStream, Reader, Source |
| */ |
| private static XmlBindings getXmlBindings(Object metadata, ClassLoader classLoader, Map<String, Object> properties) { |
| JAXBContext jaxbContext = CompilerHelper.getXmlBindingsModelContext(); |
| InputStream openedStream = null; |
| |
| try { |
| Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); |
| unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_XML); |
| unmarshaller.setProperty(UnmarshallerProperties.AUTO_DETECT_MEDIA_TYPE, true); |
| unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false); |
| unmarshaller.setEventHandler(new DefaultValidationEventHandler()); |
| if (metadata instanceof MetadataSource){ |
| return ((MetadataSource)metadata).getXmlBindings(properties, classLoader); |
| } |
| JAXBElement<XmlBindings> bindingsJaxbElement = null; |
| if (metadata instanceof XMLEventReader) { |
| bindingsJaxbElement= unmarshaller.unmarshal((XMLEventReader) metadata, XmlBindings.class); |
| } else if (metadata instanceof XMLStreamReader) { |
| bindingsJaxbElement = unmarshaller.unmarshal((XMLStreamReader) metadata, XmlBindings.class); |
| } else { |
| Source source = null; |
| if(metadata instanceof File){ |
| source = new StreamSource(new FileInputStream((File) metadata)); |
| } else if(metadata instanceof InputSource){ |
| if(((InputSource)metadata).getByteStream() != null){ |
| source = new StreamSource(((InputSource) metadata).getByteStream()); |
| }else if(((InputSource)metadata).getCharacterStream() != null){ |
| source = new StreamSource(((InputSource) metadata).getCharacterStream()); |
| } |
| }else if(metadata instanceof InputStream){ |
| source = new StreamSource((InputStream) metadata); |
| } else if (metadata instanceof Node) { |
| source = new DOMSource((Node) metadata); |
| } else if(metadata instanceof Reader){ |
| source = new StreamSource((Reader) metadata); |
| } else if(metadata instanceof Source){ |
| source = (Source)metadata; |
| } else if (metadata instanceof URL) { |
| openedStream = ((URL) metadata).openStream(); |
| source = new StreamSource(openedStream); |
| } else if (metadata instanceof String) { |
| StreamSource streamSource = new StreamSource((String)metadata); |
| try{ |
| bindingsJaxbElement = unmarshaller.unmarshal(streamSource, XmlBindings.class); |
| }catch(JAXBException e){ |
| openedStream = classLoader.getResourceAsStream((String)metadata); |
| if(openedStream != null){ |
| bindingsJaxbElement = unmarshaller.unmarshal(new StreamSource(openedStream), XmlBindings.class); |
| }else{ |
| throw org.eclipse.persistence.exceptions.JAXBException.couldNotUnmarshalMetadata(e); |
| } |
| } |
| } else{ |
| throw org.eclipse.persistence.exceptions.JAXBException.incorrectValueParameterTypeForOxmXmlKey(); |
| } |
| if(bindingsJaxbElement == null){ |
| if(source == null){ |
| throw org.eclipse.persistence.exceptions.JAXBException.incorrectValueParameterTypeForOxmXmlKey(); |
| }else{ |
| bindingsJaxbElement = unmarshaller.unmarshal(source, XmlBindings.class); |
| } |
| } |
| } |
| if(bindingsJaxbElement != null){ |
| return bindingsJaxbElement.getValue(); |
| } |
| throw org.eclipse.persistence.exceptions.JAXBException.incorrectValueParameterTypeForOxmXmlKey(); |
| }catch(jakarta.xml.bind.JAXBException ex){ |
| throw org.eclipse.persistence.exceptions.JAXBException.couldNotUnmarshalMetadata(ex); |
| }catch(IOException ioException){ |
| throw org.eclipse.persistence.exceptions.JAXBException.couldNotUnmarshalMetadata(ioException); |
| }finally{ |
| if(openedStream != null){ |
| try { |
| openedStream.close(); |
| } catch (IOException e) { |
| throw org.eclipse.persistence.exceptions.JAXBException.couldNotUnmarshalMetadata(e); |
| } |
| } |
| } |
| } |
| } |