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 */
019package org.apache.isis.viewer.restfulobjects.server;
020
021import java.io.InputStream;
022import java.util.Collections;
023import java.util.List;
024import java.util.Map;
025
026import javax.servlet.http.HttpServletRequest;
027import javax.servlet.http.HttpServletResponse;
028import javax.ws.rs.core.HttpHeaders;
029import javax.ws.rs.core.MediaType;
030import javax.ws.rs.core.Request;
031import javax.ws.rs.core.SecurityContext;
032import javax.ws.rs.core.UriInfo;
033
034import org.apache.isis.applib.annotation.Where;
035import org.apache.isis.applib.profiles.Localization;
036import org.apache.isis.core.commons.authentication.AuthenticationSession;
037import org.apache.isis.core.commons.config.IsisConfiguration;
038import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
039import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
040import org.apache.isis.core.metamodel.spec.SpecificationLoader;
041import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
042import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
043import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
044import org.apache.isis.viewer.restfulobjects.applib.client.RestfulRequest.DomainModel;
045import org.apache.isis.viewer.restfulobjects.applib.client.RestfulRequest.RequestParameter;
046import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
047import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
048import org.apache.isis.viewer.restfulobjects.server.resources.DomainResourceHelper;
049
050import com.google.common.base.Predicate;
051import com.google.common.collect.Collections2;
052import com.google.common.collect.Iterables;
053import com.google.common.collect.Lists;
054
055public class ResourceContext implements RendererContext {
056
057    private final HttpHeaders httpHeaders;
058    private final UriInfo uriInfo;
059    private final Request request;
060    private final HttpServletRequest httpServletRequest;
061    private final HttpServletResponse httpServletResponse;
062    private final SecurityContext securityContext;
063    private final Localization localization;
064
065    @SuppressWarnings("unused")
066    private final IsisConfiguration configuration;
067    private final AuthenticationSession authenticationSession;
068    private final PersistenceSession persistenceSession;
069    private final AdapterManager adapterManager;
070    private final SpecificationLoader specificationLookup;
071
072    private List<List<String>> followLinks;
073
074    private final Where where;
075    private final String queryString;
076    private JsonRepresentation readQueryStringAsMap;
077
078    //////////////////////////////////////////////////////////////////
079    // constructor and init
080    //////////////////////////////////////////////////////////////////
081
082    public ResourceContext(
083            final RepresentationType representationType, 
084            final HttpHeaders httpHeaders, 
085            final UriInfo uriInfo, 
086            final Request request, 
087            final Where where, 
088            final String queryStringIfAny,
089            final HttpServletRequest httpServletRequest, 
090            final HttpServletResponse httpServletResponse,
091            final SecurityContext securityContext, 
092            final Localization localization, final AuthenticationSession authenticationSession, 
093            final PersistenceSession persistenceSession, 
094            final AdapterManager objectAdapterLookup, 
095            final SpecificationLoader specificationLookup, 
096            final IsisConfiguration configuration) {
097
098        this.httpHeaders = httpHeaders;
099        this.uriInfo = uriInfo;
100        this.request = request;
101        this.queryString = queryStringIfAny;
102        this.httpServletRequest = httpServletRequest;
103        this.httpServletResponse = httpServletResponse;
104        this.securityContext = securityContext;
105        this.localization = localization;
106        this.configuration = configuration;
107        this.authenticationSession = authenticationSession;
108        this.persistenceSession = persistenceSession;
109        this.adapterManager = objectAdapterLookup;
110        this.specificationLookup = specificationLookup;
111        this.where = where;
112
113        init(representationType);
114    }
115
116    
117    void init(final RepresentationType representationType) {
118        getQueryStringAsJsonRepr(); // force it to be cached
119        
120        ensureCompatibleAcceptHeader(representationType);
121        ensureDomainModelQueryParamSupported();
122        
123        this.followLinks = Collections.unmodifiableList(getArg(RequestParameter.FOLLOW_LINKS));
124    }
125
126    private void ensureDomainModelQueryParamSupported() {
127        final DomainModel domainModel = getArg(RequestParameter.DOMAIN_MODEL);
128        if(domainModel != DomainModel.FORMAL) {
129            throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST,  
130                                           "x-ro-domain-model of '%s' is not supported", domainModel);
131        }
132    }
133
134    private void ensureCompatibleAcceptHeader(final RepresentationType representationType) {
135        if (representationType == null) {
136            return;
137        }
138
139        // RestEasy will check the basic media types...
140        // ... so we just need to check the profile paramter
141        final String producedProfile = representationType.getMediaTypeProfile();
142        if(producedProfile != null) {
143            for (MediaType mediaType : httpHeaders.getAcceptableMediaTypes()) {
144                String acceptedProfileValue = mediaType.getParameters().get("profile");
145                if(acceptedProfileValue == null) {
146                    continue;
147                }
148                if(!producedProfile.equals(acceptedProfileValue)) {
149                    throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_ACCEPTABLE);
150                }
151            }
152        }
153    }
154
155
156    
157    //////////////////////////////////////////////////////////////////
158    //
159    //////////////////////////////////////////////////////////////////
160    
161    
162    public HttpHeaders getHttpHeaders() {
163        return httpHeaders;
164    }
165
166    /**
167     * The {@link HttpServletRequest#getQueryString() query string}, cached
168     * after first call.
169     * 
170     * <p>
171     * Note that this can return non-null for <tt>PUT</tt>s as well as <tt>GET</tt>s.
172     * It will only have been URL encoded for the latter; the caller should handle both cases.
173     */
174    public String getQueryString() {
175        if(queryString != null) {
176            return queryString;
177        }
178        return getHttpServletRequest().getQueryString();
179    }
180
181    public JsonRepresentation getQueryStringAsJsonRepr() {
182        
183        if (readQueryStringAsMap == null) {
184            readQueryStringAsMap = requestArgsAsMap();
185        }
186        return readQueryStringAsMap;
187    }
188
189    protected JsonRepresentation requestArgsAsMap() {
190        @SuppressWarnings("unchecked")
191        final Map<String,String[]> params = httpServletRequest.getParameterMap();
192
193        if(simpleQueryArgs(params)) {
194            // try to process regular params and build up JSON repr 
195            final JsonRepresentation map = JsonRepresentation.newMap();
196            for(String paramName: params.keySet()) {
197                String paramValue = params.get(paramName)[0];
198                try {
199                    int paramValueAsInt = Integer.parseInt(paramValue);
200                    map.mapPut(paramName+".value", paramValueAsInt);
201                } catch(Exception ex) {
202                    map.mapPut(paramName+".value", paramValue);
203                }
204            }
205            return map;
206        } else {
207            return DomainResourceHelper.readQueryStringAsMap(getQueryString());
208        }
209    }
210
211    private static boolean simpleQueryArgs(Map<String, String[]> params) {
212        if(params.isEmpty()) {
213            return false;
214        }
215        for(String paramName: params.keySet()) {
216            if("x-isis-querystring".equals(paramName) || paramName.startsWith("{")) {
217                return false;
218            }
219        }
220        return true;
221    }
222
223
224    public <Q> Q getArg(final RequestParameter<Q> requestParameter) {
225        final JsonRepresentation queryStringJsonRepr = getQueryStringAsJsonRepr();
226        return requestParameter.valueOf(queryStringJsonRepr);
227    }
228
229    public UriInfo getUriInfo() {
230        return uriInfo;
231    }
232
233    public Request getRequest() {
234        return request;
235    }
236
237    public HttpServletRequest getHttpServletRequest() {
238        return httpServletRequest;
239    }
240
241    public HttpServletResponse getServletResponse() {
242        return httpServletResponse;
243    }
244
245    public SecurityContext getSecurityContext() {
246        return securityContext;
247    }
248
249
250    public List<List<String>> getFollowLinks() {
251        return followLinks;
252    }
253
254
255    
256    public Localization getLocalization() {
257        return localization;
258    }
259
260    public AuthenticationSession getAuthenticationSession() {
261        return authenticationSession;
262    }
263
264    public AdapterManager getAdapterManager() {
265        return adapterManager;
266    }
267
268    public PersistenceSession getPersistenceSession() {
269        return persistenceSession;
270    }
271
272    public List<ObjectAdapter> getServiceAdapters() {
273        return persistenceSession.getServices();
274    }
275
276    public SpecificationLoader getSpecificationLookup() {
277        return specificationLookup;
278    }
279
280    public Where getWhere() {
281        return where;
282    }
283
284
285    //////////////////////////////////////////////////////////////////
286    //
287    //////////////////////////////////////////////////////////////////
288
289    public String urlFor(final String url) {
290        return getUriInfo().getBaseUri().toString() + url;
291    }
292
293}