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.resources;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.text.DateFormat;
024import java.text.SimpleDateFormat;
025import java.util.Date;
026import java.util.List;
027import java.util.TimeZone;
028
029import javax.servlet.http.HttpServletRequest;
030import javax.servlet.http.HttpServletResponse;
031import javax.ws.rs.core.CacheControl;
032import javax.ws.rs.core.Context;
033import javax.ws.rs.core.EntityTag;
034import javax.ws.rs.core.HttpHeaders;
035import javax.ws.rs.core.MediaType;
036import javax.ws.rs.core.Request;
037import javax.ws.rs.core.Response;
038import javax.ws.rs.core.Response.ResponseBuilder;
039import javax.ws.rs.core.SecurityContext;
040import javax.ws.rs.core.UriInfo;
041
042import org.apache.isis.applib.annotation.Where;
043import org.apache.isis.applib.profiles.Localization;
044import org.apache.isis.core.commons.authentication.AuthenticationSession;
045import org.apache.isis.core.commons.config.IsisConfiguration;
046import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
047import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
048import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
049import org.apache.isis.core.metamodel.adapter.version.Version;
050import org.apache.isis.core.metamodel.services.ServiceUtil;
051import org.apache.isis.core.metamodel.spec.ActionType;
052import org.apache.isis.core.metamodel.spec.ObjectSpecification;
053import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
054import org.apache.isis.core.runtime.persistence.ObjectNotFoundException;
055import org.apache.isis.core.runtime.system.context.IsisContext;
056import org.apache.isis.core.runtime.system.persistence.OidGenerator;
057import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
058import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
059import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
060import org.apache.isis.viewer.restfulobjects.applib.util.JsonMapper;
061import org.apache.isis.viewer.restfulobjects.rendering.ReprRenderer;
062import org.apache.isis.viewer.restfulobjects.server.ResourceContext;
063import org.apache.isis.viewer.restfulobjects.server.RestfulObjectsApplicationException;
064import org.apache.isis.viewer.restfulobjects.server.util.OidUtils;
065import org.apache.isis.viewer.restfulobjects.server.util.UrlDecoderUtils;
066import org.codehaus.jackson.JsonGenerationException;
067import org.codehaus.jackson.map.JsonMappingException;
068
069public abstract class ResourceAbstract {
070
071
072    protected final static JsonMapper jsonMapper = JsonMapper.instance();
073
074    public enum Caching {
075        ONE_DAY(24 * 60 * 60), ONE_HOUR(60 * 60), NONE(0);
076
077        private final CacheControl cacheControl;
078
079        private Caching(final int maxAge) {
080            this.cacheControl = new CacheControl();
081            if (maxAge > 0) {
082                cacheControl.setMaxAge(maxAge);
083            } else {
084                cacheControl.setNoCache(true);
085            }
086        }
087
088        public CacheControl getCacheControl() {
089            return cacheControl;
090        }
091    }
092
093    // nb: SET is excluded; we simply flatten contributed actions.
094    public final static ActionType[] ACTION_TYPES = { ActionType.USER, ActionType.DEBUG, ActionType.EXPLORATION };
095
096    private final static String UTC_DATEFORMAT = "yyyy-MM-ddTHH:mm:ss.sss";
097
098    @Context
099    HttpHeaders httpHeaders;
100
101    @Context
102    UriInfo uriInfo;
103
104    @Context
105    Request request;
106
107    @Context
108    HttpServletRequest httpServletRequest;
109
110    @Context
111    HttpServletResponse httpServletResponse;
112
113    @Context
114    SecurityContext securityContext;
115
116    private ResourceContext resourceContext;
117
118    protected void init(Where where) {
119        init(RepresentationType.GENERIC, where);
120    }
121
122    protected void init(final RepresentationType representationType, Where where) {
123        init(representationType, where, (String)null);
124    }
125
126    protected void init(RepresentationType representationType, Where where, InputStream arguments) {
127        final String queryString = DomainResourceHelper.asStringUtf8(arguments);
128        init(representationType, where, queryString);
129    }
130
131    protected void init(RepresentationType representationType, Where where, String queryString) {
132        if (!IsisContext.inSession()) {
133            throw RestfulObjectsApplicationException.create(HttpStatusCode.UNAUTHORIZED);
134        } 
135        if (getAuthenticationSession() == null) {
136            throw RestfulObjectsApplicationException.create(HttpStatusCode.UNAUTHORIZED);
137        }
138
139        this.resourceContext = new ResourceContext(
140                representationType, httpHeaders, uriInfo, request, where, queryString, httpServletRequest, httpServletResponse, 
141                securityContext, getLocalization(), getAuthenticationSession(), getPersistenceSession(), getAdapterManager(), getSpecificationLoader(), getConfiguration());
142    }
143
144    protected ResourceContext getResourceContext() {
145        return resourceContext;
146    }
147
148    // //////////////////////////////////////////////////////////////
149    // Rendering
150    // //////////////////////////////////////////////////////////////
151
152    protected static String jsonFor(final Object object) {
153        try {
154            return jsonMapper.write(object);
155        } catch (final JsonGenerationException e) {
156            throw new RuntimeException(e);
157        } catch (final JsonMappingException e) {
158            throw new RuntimeException(e);
159        } catch (final IOException e) {
160            throw new RuntimeException(e);
161        }
162    }
163
164    // //////////////////////////////////////////////////////////////
165    // Isis integration
166    // //////////////////////////////////////////////////////////////
167
168    protected ObjectSpecification getSpecification(final String specFullName) {
169        return getSpecificationLoader().loadSpecification(specFullName);
170    }
171
172    protected ObjectAdapter getObjectAdapterElseThrowNotFound(String domainType, final String instanceId) {
173        ObjectAdapter objectAdapter = getObjectAdapterElseNull(domainType, instanceId);
174
175        if (objectAdapter == null) {
176            final String instanceIdUnencoded = UrlDecoderUtils.urlDecode(instanceId);
177            throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.NOT_FOUND, "could not determine adapter for OID: '%s:%s'", domainType, instanceIdUnencoded);
178        }
179        return objectAdapter;
180    }
181
182    protected ObjectAdapter getObjectAdapterElseNull(String domainType, final String instanceId) {
183        return OidUtils.getObjectAdapterElseNull(resourceContext, domainType, instanceId);
184    }
185
186    protected ObjectAdapter getServiceAdapter(final String serviceId) {
187        final List<ObjectAdapter> serviceAdapters = getPersistenceSession().getServices();
188        for (final ObjectAdapter serviceAdapter : serviceAdapters) {
189            final Object servicePojo = serviceAdapter.getObject();
190            final String id = ServiceUtil.id(servicePojo);
191            if (serviceId.equals(id)) {
192                return serviceAdapter;
193            }
194        }
195        throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.NOT_FOUND, "Could not locate service '%s'", serviceId);
196    }
197
198
199    // //////////////////////////////////////////////////////////////
200    // Responses
201    // //////////////////////////////////////////////////////////////
202
203    public static ResponseBuilder responseOfNoContent() {
204        return responseOf(HttpStatusCode.NO_CONTENT);
205    }
206
207    public static ResponseBuilder responseOfOk(final ReprRenderer<?, ?> renderer, final Caching caching) {
208        return responseOfOk(renderer, caching, null);
209    }
210
211    public static ResponseBuilder responseOfOk(final ReprRenderer<?, ?> renderer, final Caching caching, final Version version) {
212        final MediaType mediaType = renderer.getMediaType();
213        final ResponseBuilder response = responseOf(HttpStatusCode.OK).type(mediaType).cacheControl(caching.getCacheControl()).entity(jsonFor(renderer.render()));
214        return addLastModifiedAndETagIfAvailable(response, version);
215    }
216
217    protected static ResponseBuilder responseOf(final HttpStatusCode httpStatusCode) {
218        return Response.status(httpStatusCode.getJaxrsStatusType()).type(MediaType.APPLICATION_JSON_TYPE);
219    }
220
221    public static ResponseBuilder addLastModifiedAndETagIfAvailable(final ResponseBuilder responseBuilder, final Version version) {
222        if (version != null && version.getTime() != null) {
223            final Date time = version.getTime();
224            responseBuilder.lastModified(time);
225            responseBuilder.tag(asETag(time));
226        }
227        return responseBuilder;
228    }
229
230    private static EntityTag asETag(final Date time) {
231        final SimpleDateFormat sdf = new SimpleDateFormat(UTC_DATEFORMAT);
232        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
233        final String utcTime = sdf.format(time);
234        return new EntityTag(utcTime, true);
235    }
236
237    // //////////////////////////////////////////////////////////////
238    // Dependencies (from singletons)
239    // //////////////////////////////////////////////////////////////
240
241    protected IsisConfiguration getConfiguration () {
242        return IsisContext.getConfiguration();
243    }
244
245    protected AuthenticationSession getAuthenticationSession() {
246        return IsisContext.getAuthenticationSession();
247    }
248
249    protected SpecificationLoaderSpi getSpecificationLoader() {
250        return IsisContext.getSpecificationLoader();
251    }
252
253    protected AdapterManager getAdapterManager() {
254        return getPersistenceSession().getAdapterManager();
255    }
256
257    protected PersistenceSession getPersistenceSession() {
258        return IsisContext.getPersistenceSession();
259    }
260
261    protected Localization getLocalization() {
262        return IsisContext.getLocalization();
263    }
264
265    protected OidMarshaller getOidMarshaller() {
266        return IsisContext.getOidMarshaller();
267    }
268
269    // //////////////////////////////////////////////////////////////
270    // Dependencies (injected via @Context)
271    // //////////////////////////////////////////////////////////////
272
273    protected HttpServletRequest getServletRequest() {
274        return getResourceContext().getHttpServletRequest();
275    }
276
277}