/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.isis.viewer.restfulobjects.server; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Request; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.Providers; import com.google.common.base.Strings; import com.google.common.collect.Sets; import org.apache.isis.applib.annotation.Where; import org.apache.isis.core.commons.authentication.AuthenticationSession; import org.apache.isis.core.commons.config.IsisConfiguration; import org.apache.isis.core.metamodel.adapter.ObjectAdapter; import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager; import org.apache.isis.core.metamodel.adapter.oid.Oid; import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy; import org.apache.isis.core.metamodel.deployment.DeploymentCategory; import org.apache.isis.core.metamodel.services.ServicesInjector; import org.apache.isis.core.metamodel.specloader.SpecificationLoader; import org.apache.isis.core.runtime.system.persistence.PersistenceSession; import org.apache.isis.core.runtime.system.session.IsisSessionFactory; import org.apache.isis.core.webapp.WebAppConstants; import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; import org.apache.isis.viewer.restfulobjects.applib.RepresentationType; import org.apache.isis.viewer.restfulobjects.applib.client.RestfulRequest.DomainModel; import org.apache.isis.viewer.restfulobjects.applib.client.RestfulRequest.RequestParameter; import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode; import org.apache.isis.viewer.restfulobjects.rendering.RendererContext6; import org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplicationException; import org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService; import org.apache.isis.viewer.restfulobjects.rendering.util.Util; public class ResourceContext implements RendererContext6 { private final HttpHeaders httpHeaders; private final UriInfo uriInfo; private final Request request; private final Providers providers; private final HttpServletRequest httpServletRequest; private final HttpServletResponse httpServletResponse; private final SecurityContext securityContext; private final DeploymentCategory deploymentCategory; private final IsisConfiguration configuration; private final ServicesInjector servicesInjector; private final SpecificationLoader specificationLoader; private final AuthenticationSession authenticationSession; private final PersistenceSession persistenceSession; private List<List<String>> followLinks; private final Where where; private final RepresentationService.Intent intent; private final InteractionInitiatedBy interactionInitiatedBy; private final String urlUnencodedQueryString; private JsonRepresentation readQueryStringAsMap; //region > constructor and init public ResourceContext( final RepresentationType representationType, final HttpHeaders httpHeaders, final Providers providers, final UriInfo uriInfo, final Request request, final Where where, final RepresentationService.Intent intent, final String urlUnencodedQueryStringIfAny, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final SecurityContext securityContext, final InteractionInitiatedBy interactionInitiatedBy) { this.httpHeaders = httpHeaders; this.providers = providers; this.uriInfo = uriInfo; this.request = request; this.where = where; this.intent = intent; this.urlUnencodedQueryString = urlUnencodedQueryStringIfAny; this.httpServletRequest = httpServletRequest; this.httpServletResponse = httpServletResponse; this.securityContext = securityContext; final ServletContext servletContext = httpServletRequest.getServletContext(); final IsisSessionFactory isisSessionFactory = (IsisSessionFactory)servletContext.getAttribute(WebAppConstants.ISIS_SESSION_FACTORY); this.servicesInjector = isisSessionFactory.getServicesInjector(); this.configuration = isisSessionFactory.getConfiguration(); this.authenticationSession = isisSessionFactory.getCurrentSession().getAuthenticationSession(); this.specificationLoader = isisSessionFactory.getSpecificationLoader(); this.deploymentCategory = isisSessionFactory.getDeploymentCategory(); this.persistenceSession = isisSessionFactory.getCurrentSession().getPersistenceSession(); this.interactionInitiatedBy = interactionInitiatedBy; init(representationType); } void init(final RepresentationType representationType) { getQueryStringAsJsonRepr(); // force it to be cached // previously we checked for compatible accept headers here. // now, though, this is a responsibility of the various ContentNegotiationService implementations ensureDomainModelQueryParamSupported(); this.followLinks = Collections.unmodifiableList(getArg(RequestParameter.FOLLOW_LINKS)); } private void ensureDomainModelQueryParamSupported() { final DomainModel domainModel = getArg(RequestParameter.DOMAIN_MODEL); if(domainModel != DomainModel.FORMAL) { throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST, "x-ro-domain-model of '%s' is not supported", domainModel); } } //endregion public HttpHeaders getHttpHeaders() { return httpHeaders; } /** * Note that this can return non-null for all HTTP methods; will be either the * query string (GET, DELETE) or read out of the input stream (PUT, POST). */ public String getUrlUnencodedQueryString() { return urlUnencodedQueryString; } public JsonRepresentation getQueryStringAsJsonRepr() { if (readQueryStringAsMap == null) { readQueryStringAsMap = requestArgsAsMap(); } return readQueryStringAsMap; } protected JsonRepresentation requestArgsAsMap() { @SuppressWarnings("unchecked") final Map<String,String[]> params = httpServletRequest.getParameterMap(); if(simpleQueryArgs(params)) { // try to process regular params and build up JSON repr final JsonRepresentation map = JsonRepresentation.newMap(); for(String paramName: params.keySet()) { String paramValue = params.get(paramName)[0]; // this is rather hacky :-( final String key = paramName.startsWith("x-ro") ? paramName : paramName + ".value"; try { // and this is even more hacky :-( int paramValueAsInt = Integer.parseInt(paramValue); map.mapPut(key, paramValueAsInt); } catch(Exception ex) { map.mapPut(key, stripQuotes(paramValue)); } } return map; } else { final String queryString = getUrlUnencodedQueryString(); return Util.readQueryStringAsMap(queryString); } } static String stripQuotes(final String str) { if(Strings.isNullOrEmpty(str)) { return str; } if(str.startsWith("\"") && str.endsWith("\"")) { return str.substring(1, str.lastIndexOf("\"")); } return str; } private static boolean simpleQueryArgs(Map<String, String[]> params) { if(params.isEmpty()) { return false; } for(String paramName: params.keySet()) { if("x-isis-querystring".equals(paramName) || paramName.startsWith("{")) { return false; } } return true; } public <Q> Q getArg(final RequestParameter<Q> requestParameter) { final JsonRepresentation queryStringJsonRepr = getQueryStringAsJsonRepr(); return requestParameter.valueOf(queryStringJsonRepr); } public UriInfo getUriInfo() { return uriInfo; } public Request getRequest() { return request; } public HttpServletRequest getHttpServletRequest() { return httpServletRequest; } @Override public List<MediaType> getAcceptableMediaTypes() { return httpHeaders.getAcceptableMediaTypes(); } public HttpServletResponse getServletResponse() { return httpServletResponse; } public SecurityContext getSecurityContext() { return securityContext; } @Override public DeploymentCategory getDeploymentCategory() { return deploymentCategory; } @Override public InteractionInitiatedBy getInteractionInitiatedBy() { return interactionInitiatedBy; } @Override public List<List<String>> getFollowLinks() { return followLinks; } @Override public AuthenticationSession getAuthenticationSession() { return authenticationSession; } /** * @deprecated - use {@link #getPersistenceSession()}. */ @Deprecated @Override public AdapterManager getAdapterManager() { return persistenceSession; } @Override public ServicesInjector getServicesInjector() { return servicesInjector; } @Override public PersistenceSession getPersistenceSession() { return persistenceSession; } public List<ObjectAdapter> getServiceAdapters() { return persistenceSession.getServices(); } @Override public SpecificationLoader getSpecificationLoader() { return specificationLoader; } @Override public IsisConfiguration getConfiguration() { return configuration; } @Override public Where getWhere() { return where; } /** * Only applies to rendering of objects * @return */ @Override public RepresentationService.Intent getIntent() { return intent; } //region > canEagerlyRender private Set<Oid> rendered = Sets.newHashSet(); @Override public boolean canEagerlyRender(ObjectAdapter objectAdapter) { final Oid oid = objectAdapter.getOid(); return rendered.add(oid); } //endregion //region > configuration settings private static final boolean HONOR_UI_HINTS_DEFAULT = false; private static final boolean OBJECT_PROPERTY_VALUES_ONLY_DEFAULT = false; private static final boolean SUPPRESS_DESCRIBED_BY_LINKS_DEFAULT = false; private static final boolean SUPPRESS_UPDATE_LINK_DEFAULT = false; private static final boolean SUPPRESS_MEMBER_ID_DEFAULT = false; private static final boolean SUPPRESS_MEMBER_LINKS_DEFAULT = false; private static final boolean SUPPRESS_MEMBER_EXTENSIONS_DEFAULT = false; private static final boolean SUPPRESS_MEMBER_DISABLED_REASON_DEFAULT = false; @Override public boolean honorUiHints() { return getConfiguration().getBoolean("isis.viewer.restfulobjects.honorUiHints", HONOR_UI_HINTS_DEFAULT); } @Override public boolean objectPropertyValuesOnly() { return getConfiguration().getBoolean("isis.viewer.restfulobjects.objectPropertyValuesOnly", OBJECT_PROPERTY_VALUES_ONLY_DEFAULT); } @Override public boolean suppressDescribedByLinks() { return getConfiguration().getBoolean("isis.viewer.restfulobjects.suppressDescribedByLinks", SUPPRESS_DESCRIBED_BY_LINKS_DEFAULT); } @Override public boolean suppressUpdateLink() { return getConfiguration().getBoolean("isis.viewer.restfulobjects.suppressUpdateLink", SUPPRESS_UPDATE_LINK_DEFAULT); } @Override public boolean suppressMemberId() { return getConfiguration().getBoolean("isis.viewer.restfulobjects.suppressMemberId", SUPPRESS_MEMBER_ID_DEFAULT); } @Override public boolean suppressMemberLinks() { return getConfiguration().getBoolean("isis.viewer.restfulobjects.suppressMemberLinks", SUPPRESS_MEMBER_LINKS_DEFAULT); } @Override public boolean suppressMemberExtensions() { return getConfiguration().getBoolean("isis.viewer.restfulobjects.suppressMemberExtensions", SUPPRESS_MEMBER_EXTENSIONS_DEFAULT); } @Override public boolean suppressMemberDisabledReason() { return getConfiguration().getBoolean("isis.viewer.restfulobjects.suppressMemberDisabledReason", SUPPRESS_MEMBER_DISABLED_REASON_DEFAULT); } //endregion @Override public String urlFor(final String url) { return getUriInfo().getBaseUri().toString() + url; } }