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.util.Collection;
022
023import javax.ws.rs.GET;
024import javax.ws.rs.Path;
025import javax.ws.rs.PathParam;
026import javax.ws.rs.Produces;
027import javax.ws.rs.QueryParam;
028import javax.ws.rs.core.MediaType;
029import javax.ws.rs.core.Response;
030
031import com.google.common.base.Strings;
032
033import org.apache.isis.applib.annotation.Where;
034import org.apache.isis.core.metamodel.spec.ObjectSpecId;
035import org.apache.isis.core.metamodel.spec.ObjectSpecification;
036import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
037import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
038import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
039import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
040import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
041import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
042import org.apache.isis.viewer.restfulobjects.applib.Rel;
043import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
044import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
045import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse.HttpStatusCode;
046import org.apache.isis.viewer.restfulobjects.applib.domaintypes.DomainTypeResource;
047import org.apache.isis.viewer.restfulobjects.rendering.LinkBuilder;
048import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.ActionDescriptionReprRenderer;
049import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.ActionParameterDescriptionReprRenderer;
050import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.CollectionDescriptionReprRenderer;
051import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.DomainTypeReprRenderer;
052import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.ParentSpecAndAction;
053import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.ParentSpecAndActionParam;
054import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.ParentSpecAndCollection;
055import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.ParentSpecAndProperty;
056import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.PropertyDescriptionReprRenderer;
057import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.TypeActionResultReprRenderer;
058import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.TypeListReprRenderer;
059import org.apache.isis.viewer.restfulobjects.server.RestfulObjectsApplicationException;
060import org.apache.isis.viewer.restfulobjects.server.util.UrlParserUtils;
061
062/**
063 * Implementation note: it seems to be necessary to annotate the implementation
064 * with {@link Path} rather than the interface (at least under RestEasy 1.0.2
065 * and 1.1-RC2).
066 */
067@Path("/domain-types")
068public class DomainTypeResourceServerside extends ResourceAbstract implements DomainTypeResource {
069
070    @Override
071    @GET
072    @Path("/")
073    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_TYPE_LIST })
074    public Response domainTypes() {
075        final RepresentationType representationType = RepresentationType.TYPE_LIST;
076        init(representationType, Where.ANYWHERE);
077
078        final Collection<ObjectSpecification> allSpecifications = getSpecificationLoader().allSpecifications();
079
080        final TypeListReprRenderer renderer = new TypeListReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
081        renderer.with(allSpecifications).includesSelf();
082
083        return responseOfOk(renderer, Caching.ONE_DAY).build();
084    }
085
086    @Override
087    @GET
088    @Path("/{domainType}")
089    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_DOMAIN_TYPE })
090    public Response domainType(@PathParam("domainType") final String domainType) {
091
092        init(RepresentationType.DOMAIN_TYPE, Where.ANYWHERE);
093
094        final ObjectSpecification objectSpec = getSpecificationLoader().lookupBySpecId(ObjectSpecId.of(domainType));
095
096        final DomainTypeReprRenderer renderer = new DomainTypeReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
097        renderer.with(objectSpec).includesSelf();
098
099        return responseOfOk(renderer, Caching.ONE_DAY).build();
100    }
101
102    @Override
103    @GET
104    @Path("/{domainType}/properties/{propertyId}")
105    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_PROPERTY_DESCRIPTION })
106    public Response typeProperty(@PathParam("domainType") final String domainType, @PathParam("propertyId") final String propertyId) {
107        final RepresentationType representationType = RepresentationType.PROPERTY_DESCRIPTION;
108        init(representationType, Where.ANYWHERE);
109
110        final ObjectSpecification parentSpec = getSpecificationLoader().lookupBySpecId(ObjectSpecId.of(domainType));
111        if (parentSpec == null) {
112            throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
113        }
114
115        final ObjectMember objectMember = parentSpec.getAssociation(propertyId);
116        if (objectMember == null || objectMember.isOneToManyAssociation()) {
117            throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
118        }
119        final OneToOneAssociation property = (OneToOneAssociation) objectMember;
120
121        final PropertyDescriptionReprRenderer renderer = new PropertyDescriptionReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
122        renderer.with(new ParentSpecAndProperty(parentSpec, property)).includesSelf();
123
124        return responseOfOk(renderer, Caching.ONE_DAY).build();
125    }
126
127    @Override
128    @GET
129    @Path("/{domainType}/collections/{collectionId}")
130    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_COLLECTION_DESCRIPTION })
131    public Response typeCollection(@PathParam("domainType") final String domainType, @PathParam("collectionId") final String collectionId) {
132        final RepresentationType representationType = RepresentationType.COLLECTION_DESCRIPTION;
133        init(representationType, Where.ANYWHERE);
134
135        final ObjectSpecification parentSpec = getSpecificationLoader().lookupBySpecId(ObjectSpecId.of(domainType));
136        if (parentSpec == null) {
137            throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
138        }
139
140        final ObjectMember objectMember = parentSpec.getAssociation(collectionId);
141        if (objectMember == null || objectMember.isOneToOneAssociation()) {
142            throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
143        }
144        final OneToManyAssociation collection = (OneToManyAssociation) objectMember;
145
146        final CollectionDescriptionReprRenderer renderer = new CollectionDescriptionReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
147        renderer.with(new ParentSpecAndCollection(parentSpec, collection)).includesSelf();
148
149        return responseOfOk(renderer, Caching.ONE_DAY).build();
150    }
151
152    @Override
153    @GET
154    @Path("/{domainType}/actions/{actionId}")
155    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_ACTION_DESCRIPTION })
156    public Response typeAction(@PathParam("domainType") final String domainType, @PathParam("actionId") final String actionId) {
157        final RepresentationType representationType = RepresentationType.ACTION_DESCRIPTION;
158        init(representationType, Where.ANYWHERE);
159
160        final ObjectSpecification parentSpec = getSpecificationLoader().lookupBySpecId(ObjectSpecId.of(domainType));
161        if (parentSpec == null) {
162            throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
163        }
164
165        final ObjectMember objectMember = parentSpec.getObjectAction(actionId);
166        if (objectMember == null) {
167            throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
168        }
169        final ObjectAction action = (ObjectAction) objectMember;
170
171        final ActionDescriptionReprRenderer renderer = new ActionDescriptionReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
172        renderer.with(new ParentSpecAndAction(parentSpec, action)).includesSelf();
173
174        return responseOfOk(renderer, Caching.ONE_DAY).build();
175    }
176
177    @Override
178    @GET
179    @Path("/{domainType}/actions/{actionId}/params/{paramName}")
180    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_ACTION_PARAMETER_DESCRIPTION })
181    public Response typeActionParam(@PathParam("domainType") final String domainType, @PathParam("actionId") final String actionId, @PathParam("paramName") final String paramName) {
182        final RepresentationType representationType = RepresentationType.ACTION_PARAMETER_DESCRIPTION;
183        init(representationType, Where.ANYWHERE);
184
185        final ObjectSpecification parentSpec = getSpecificationLoader().lookupBySpecId(ObjectSpecId.of(domainType));
186        if (parentSpec == null) {
187            throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
188        }
189
190        final ObjectMember objectMember = parentSpec.getObjectAction(actionId);
191        if (objectMember == null) {
192            throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
193        }
194        final ObjectAction parentAction = (ObjectAction) objectMember;
195
196        final ObjectActionParameter actionParam = parentAction.getParameterByName(paramName);
197
198        final ActionParameterDescriptionReprRenderer renderer = new ActionParameterDescriptionReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
199        renderer.with(new ParentSpecAndActionParam(parentSpec, actionParam)).includesSelf();
200
201        return responseOfOk(renderer, Caching.ONE_DAY).build();
202    }
203
204    // //////////////////////////////////////////////////////////
205    // domain type actions
206    // //////////////////////////////////////////////////////////
207
208    @Override
209    @GET
210    @Path("/{domainType}/type-actions/isSubtypeOf/invoke")
211    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_TYPE_ACTION_RESULT, RestfulMediaType.APPLICATION_JSON_ERROR })
212    public Response domainTypeIsSubtypeOf(
213            @PathParam("domainType") final String domainType, 
214            @QueryParam("supertype") final String superTypeStr, // simple style
215            @QueryParam("args") final String args // formal style
216            ) {
217        init(Where.ANYWHERE);
218
219        final String supertype = domainTypeFor(superTypeStr, args, "supertype");
220
221        final ObjectSpecification domainTypeSpec = getSpecificationLoader().lookupBySpecId(ObjectSpecId.of(domainType));
222        final ObjectSpecification supertypeSpec = getSpecificationLoader().lookupBySpecId(ObjectSpecId.of(supertype));
223
224        final TypeActionResultReprRenderer renderer = new TypeActionResultReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
225
226        final String url = "domain-types/" + domainType + "/type-actions/isSubtypeOf/invoke";
227        final LinkBuilder linkBuilder = LinkBuilder.newBuilder(getResourceContext(), Rel.SELF.getName(), RepresentationType.TYPE_ACTION_RESULT, url);
228        final JsonRepresentation arguments = DomainTypeReprRenderer.argumentsTo(getResourceContext(), "supertype", supertypeSpec);
229        final JsonRepresentation selfLink = linkBuilder.withArguments(arguments).build();
230
231        final boolean value = domainTypeSpec.isOfType(supertypeSpec);
232        renderer.with(domainTypeSpec).withSelf(selfLink).withValue(value);
233
234        return responseOfOk(renderer, Caching.ONE_DAY).build();
235    }
236
237
238    @Override
239    @GET
240    @Path("/{domainType}/type-actions/isSupertypeOf/invoke")
241    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_TYPE_ACTION_RESULT, RestfulMediaType.APPLICATION_JSON_ERROR })
242    public Response domainTypeIsSupertypeOf(
243            @PathParam("domainType") final String domainType, 
244            @QueryParam("subtype") final String subTypeStr, // simple style
245            @QueryParam("args") final String args // formal style
246            ) {
247
248        init(Where.ANYWHERE);
249
250        final String subtype = domainTypeFor(subTypeStr, args, "subtype");
251
252        final ObjectSpecification domainTypeSpec = getSpecificationLoader().lookupBySpecId(ObjectSpecId.of(domainType));
253        final ObjectSpecification subtypeSpec = getSpecificationLoader().lookupBySpecId(ObjectSpecId.of(subtype));
254
255        final TypeActionResultReprRenderer renderer = new TypeActionResultReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
256
257        final String url = "domain-types/" + domainType + "/type-actions/isSupertypeOf/invoke";
258        final LinkBuilder linkBuilder = LinkBuilder.newBuilder(getResourceContext(), Rel.SELF.getName(), RepresentationType.TYPE_ACTION_RESULT, url);
259        final JsonRepresentation arguments = DomainTypeReprRenderer.argumentsTo(getResourceContext(), "subtype", subtypeSpec);
260        final JsonRepresentation selfLink = linkBuilder.withArguments(arguments).build();
261
262        final boolean value = subtypeSpec.isOfType(domainTypeSpec);
263        renderer.with(domainTypeSpec).withSelf(selfLink).withValue(value);
264
265        return responseOfOk(renderer, Caching.ONE_DAY).build();
266    }
267
268    private static String domainTypeFor(final String domainTypeStr, final String argumentsQueryString, final String argsParamName) {
269        // simple style; simple return
270        if (!Strings.isNullOrEmpty(domainTypeStr)) {
271            return domainTypeStr;
272        }
273
274        // formal style; must parse from args that has a link with an href to
275        // the domain type
276        final String href = linkFromFormalArgs(argumentsQueryString, argsParamName);
277        return UrlParserUtils.domainTypeFrom(href);
278    }
279
280    private static String linkFromFormalArgs(final String argumentsQueryString, final String paramName) {
281        final JsonRepresentation arguments = DomainResourceHelper.readQueryStringAsMap(argumentsQueryString);
282        if (!arguments.isLink(paramName)) {
283            throw RestfulObjectsApplicationException.createWithMessage(HttpStatusCode.BAD_REQUEST, "Args should contain a link '%s'", paramName);
284        }
285
286        return arguments.getLink(paramName).getHref();
287    }
288
289}