blob: 8532baac43184c46edd5b1d3903da5af03767021 [file] [log] [blame]
/*
* Copyright (c) 2011, 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:
// gonural - Initial implementation
package org.eclipse.persistence.jpa.rs.resources.common;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ServiceLoader;
import java.util.UUID;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.PathSegment;
import jakarta.ws.rs.core.UriInfo;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.Attribute;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.Descriptor;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.ItemLinks;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.Link;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.LinkTemplate;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.LinkV2;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.PersistenceUnit;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.Query;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.v2.ContextsCatalog;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.v2.MetadataCatalog;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.v2.Property;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.v2.Reference;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.v2.Resource;
import org.eclipse.persistence.internal.jpa.rs.metadata.model.v2.ResourceSchema;
import org.eclipse.persistence.jaxb.JAXBContext;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.MarshallerProperties;
import org.eclipse.persistence.jpa.rs.DataStorage;
import org.eclipse.persistence.jpa.rs.MatrixParameters;
import org.eclipse.persistence.jpa.rs.PersistenceContext;
import org.eclipse.persistence.jpa.rs.PersistenceContextFactory;
import org.eclipse.persistence.jpa.rs.PersistenceContextFactoryProvider;
import org.eclipse.persistence.jpa.rs.QueryParameters;
import org.eclipse.persistence.jpa.rs.exceptions.JPARSException;
import org.eclipse.persistence.jpa.rs.features.ServiceVersion;
import org.eclipse.persistence.jpa.rs.util.JPARSLogger;
import org.eclipse.persistence.jpa.rs.util.list.LinkList;
import org.eclipse.persistence.jpa.rs.util.list.QueryList;
/**
* Base class for all resources.
*
* @author gonural
*/
public abstract class AbstractResource {
public static final String SERVICE_VERSION_FORMAT = "v\\d\\.\\d|latest";
public static final String APPLICATION_SCHEMA_JSON ="application/schema+json";
public static final MediaType APPLICATION_SCHEMA_JSON_TYPE = new MediaType("application","schema+json");
protected PersistenceContextFactory factory;
/**
* Sets the persistence factory.
*
* @param factory the new persistence factory
*/
public void setPersistenceFactory(PersistenceContextFactory factory) {
this.factory = factory;
}
/**
* Gets the persistence factory.
*
* @return the persistence factory
*/
public PersistenceContextFactory getPersistenceFactory() {
return getPersistenceFactory(Thread.currentThread().getContextClassLoader());
}
/**
* Gets the persistence factory.
*
* @return the persistence factory
*/
public PersistenceContextFactory getPersistenceFactory(ClassLoader loader) {
if (factory == null) {
factory = buildPersistenceContextFactory(loader);
}
return factory;
}
/**
* Builds the persistence context factory.
*
* @param loader the loader
* @return the persistence context factory
*/
protected PersistenceContextFactory buildPersistenceContextFactory(ClassLoader loader) {
ServiceLoader<PersistenceContextFactoryProvider> contextFactoryLoader = ServiceLoader.load(PersistenceContextFactoryProvider.class, loader);
for (PersistenceContextFactoryProvider provider : contextFactoryLoader) {
PersistenceContextFactory factory = provider.getPersistenceContextFactory(null);
if (factory != null) {
return factory;
}
}
return null;
}
/**
* Get a map of the matrix parameters associated with the URI path segment of the current request
*
* In JPA-RS, things that user sets (such as parameters of named queries, etc.) are treated as matrix parameters
* List of valid matrix parameters for JPA-RS is defined in MatrixParameters
* @see MatrixParameters
*
* @param info the info
* @param segment the segment
* @return the matrix parameters
*/
protected static Map<String, String> getMatrixParameters(UriInfo info, String segment) {
Map<String, String> matrixParameters = new HashMap<>();
for (PathSegment pathSegment : info.getPathSegments()) {
if (pathSegment.getPath() != null && pathSegment.getPath().equals(segment)) {
for (Entry<String, List<String>> entry : pathSegment.getMatrixParameters().entrySet()) {
matrixParameters.put(entry.getKey(), entry.getValue().get(0));
}
return matrixParameters;
}
}
return matrixParameters;
}
/**
* Get the URI query parameters of the current request
*
* In JPA-RS, predefined attributes (such as eclipselink query hints) are treated as query parameters
* List of valid query parameters for JPA-RS is defined in QueryParameters
* @see QueryParameters
*
* @param info the info
* @return the query parameters
*/
public static Map<String, Object> getQueryParameters(UriInfo info) {
Map<String, Object> queryParameters = new HashMap<>();
for (String key : info.getQueryParameters().keySet()) {
queryParameters.put(key, info.getQueryParameters().getFirst(key));
}
return queryParameters;
}
/**
* Checks if is valid version.
*
* @param version the version
* @return true, if is valid version
*/
protected static boolean isValidVersion(String version) {
return ServiceVersion.hasCode(version);
}
/**
* Gets the persistence context.
*
* @param persistenceUnit the persistence unit
* @param baseURI the base uri
* @param version the version
* @param initializationProperties the initialization properties
* @return the persistence context
*/
protected PersistenceContext getPersistenceContext(String persistenceUnit, String entityType, URI baseURI, String version, Map<String, Object> initializationProperties) {
if (!isValidVersion(version)) {
JPARSLogger.error("unsupported_service_version_in_the_request", new Object[] { version });
throw new IllegalArgumentException();
}
PersistenceContext context = getPersistenceFactory().get(persistenceUnit, baseURI, version, initializationProperties);
if (context == null) {
JPARSLogger.error("jpars_could_not_find_persistence_context", new Object[] { persistenceUnit });
throw JPARSException.persistenceContextCouldNotBeBootstrapped(persistenceUnit);
}
if ((entityType != null) && (context.getClass(entityType) == null)) {
JPARSLogger.error(context.getSessionLog(), "jpars_could_not_find_class_in_persistence_unit", new Object[] { entityType, persistenceUnit });
throw JPARSException.classOrClassDescriptorCouldNotBeFoundForEntity(entityType, persistenceUnit);
}
return context;
}
/**
* Gets the relationship partner.
*
* @param matrixParams the matrix params
* @param queryParams the query params
* @return the relationship partner
*/
protected String getRelationshipPartner(Map<String, String> matrixParams, Map<String, Object> queryParams) {
String partner = null;
// Fix for Bug 396791 - JPA-RS: partner should be treated as a query parameter
// For backwards compatibility, we check both, matrix and query parameters.
if ((queryParams != null) && (!queryParams.isEmpty())) {
partner = (String) queryParams.get(QueryParameters.JPARS_RELATIONSHIP_PARTNER);
}
if (partner == null) {
if ((matrixParams != null) && (!matrixParams.isEmpty())) {
partner = matrixParams.get(MatrixParameters.JPARS_RELATIONSHIP_PARTNER);
}
}
return partner;
}
/**
* Marshall metadata.
*
* @param metadata the metadata
* @param mediaType the media type
* @return the string
* @throws JAXBException the jAXB exception
*/
protected String marshallMetadata(Object metadata, String mediaType) throws JAXBException {
final Class<?>[] jaxbClasses = new Class[] { Link.class, Attribute.class, Descriptor.class, LinkTemplate.class, PersistenceUnit.class, Query.class, LinkList.class, QueryList.class,
ResourceSchema.class, Property.class, Reference.class, LinkV2.class, MetadataCatalog.class, Resource.class, ItemLinks.class, ContextsCatalog.class };
final JAXBContext context = (JAXBContext) JAXBContextFactory.createContext(jaxbClasses, null);
final Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, Boolean.FALSE);
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, mediaType);
marshaller.setProperty(MarshallerProperties.JSON_REDUCE_ANY_ARRAYS, true);
final StringWriter writer = new StringWriter();
marshaller.marshal(metadata, writer);
return writer.toString();
}
@SuppressWarnings("unused")
private static String getEncodedUri(String uri) {
try {
return URLEncoder.encode(uri, "UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO: Log it
return uri;
}
}
@SuppressWarnings("unused")
private static String getDecodedUri(String uri) {
try {
return URLDecoder.decode(uri, "UTF-8");
} catch (UnsupportedEncodingException e) {
// TODO: Log it
return uri;
}
}
protected void setRequestUniqueId() {
DataStorage.set(DataStorage.REQUEST_ID, UUID.randomUUID().toString());
}
}