/* * RESTHeart - the Web API for MongoDB * Copyright (C) SoftInstigate Srl * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.restheart.handlers.database; import org.restheart.Configuration; import org.restheart.hal.Link; import org.restheart.hal.Representation; import org.restheart.handlers.IllegalQueryParamenterException; import org.restheart.handlers.RequestContext; import org.restheart.utils.URLUtils; import io.undertow.server.HttpServerExchange; import java.time.Instant; import java.util.List; import org.bson.BsonDocument; import org.bson.BsonInt32; import org.bson.BsonString; import org.bson.BsonValue; import org.bson.types.ObjectId; import org.restheart.hal.AbstractRepresentationFactory; import org.restheart.handlers.RequestContext.TYPE; import org.restheart.handlers.collection.CollectionRepresentationFactory; import org.slf4j.LoggerFactory; import org.slf4j.Logger; /** * * @author Andrea Di Cesare {@literal <andrea@softinstigate.com>} */ public class DBRepresentationFactory extends AbstractRepresentationFactory { private static final Logger LOGGER = LoggerFactory.getLogger(DBRepresentationFactory.class); public DBRepresentationFactory() { } @Override public Representation getRepresentation( HttpServerExchange exchange, RequestContext context, List<BsonDocument> embeddedData, long size) throws IllegalQueryParamenterException { final String requestPath = buildRequestPath(exchange); final Representation rep; if (context.isFullHalMode()) { rep = createRepresentation(exchange, context, requestPath); } else { rep = createRepresentation(exchange, context, null); } if (!context.isNoProps()) { addProperties(rep, context); } addSizeAndTotalPagesProperties(size, context, rep); addEmbeddedData(embeddedData, context, rep, requestPath); if (context.isFullHalMode()) { addPaginationLinks(exchange, context, size, rep); addSpecialProperties(rep, context.getType(), context.getDbProps()); addLinkTemplates(context, rep, requestPath); // curies rep.addLink(new Link("rh", "curies", Configuration.RESTHEART_ONLINE_DOC_URL + "/{rel}.html", true), true); } return rep; } private void addProperties( final Representation rep, RequestContext context) { final BsonDocument dbProps = context.getDbProps(); rep.addProperties(dbProps); } public static void addSpecialProperties( final Representation rep, RequestContext.TYPE type, BsonDocument data) { rep.addProperty("_type", new BsonString(type.name())); Object etag = data.get("_etag"); if (etag != null && etag instanceof ObjectId) { if (data.get("_lastupdated_on") == null) { // add the _lastupdated_on in case the _etag field is present and its value is an ObjectId rep.addProperty("_lastupdated_on", new BsonString(Instant.ofEpochSecond(((ObjectId) etag) .getTimestamp()).toString())); } } } private void addEmbeddedData( final List<BsonDocument> embeddedData, final RequestContext context, final Representation rep, final String requestPath) { if (embeddedData != null) { addReturnedProperty(embeddedData, rep); if (!embeddedData.isEmpty()) { embeddedCollections(embeddedData, context, requestPath, rep); } } else { rep.addProperty("_returned", new BsonInt32(0)); } } private void addLinkTemplates( final RequestContext context, final Representation rep, final String requestPath) { String parentPath = URLUtils.getParentPath(requestPath); // link templates and curies if (context.isParentAccessible()) { // this can happen due to mongo-mounts mapped URL rep.addLink(new Link("rh:root", parentPath)); } if (parentPath.endsWith("/")) { rep.addLink(new Link("rh:db", URLUtils.removeTrailingSlashes( URLUtils.getParentPath(requestPath)) + "{dbname}", true)); } else { rep.addLink(new Link("rh:db", URLUtils.removeTrailingSlashes( URLUtils.getParentPath(requestPath)) + "/{dbname}", true)); } rep.addLink(new Link("rh:coll", requestPath + "/{collname}", true)); rep.addLink(new Link("rh:bucket", requestPath + "/{bucketname}" + RequestContext.FS_FILES_SUFFIX, true)); rep.addLink(new Link("rh:paging", requestPath + "{?page}{&pagesize}", true)); rep.addLink(new Link("rh", "curies", Configuration.RESTHEART_ONLINE_DOC_URL + "/{rel}.html", true), true); } private void embeddedCollections( final List<BsonDocument> embeddedData, final RequestContext context, final String requestPath, final Representation rep) { embeddedData.stream().forEach((d) -> { BsonValue _id = d.get("_id"); if (_id != null && _id.isString()) { BsonString id = _id.asString(); // avoid starting double slash in self href for root URI String rp = URLUtils.removeTrailingSlashes(requestPath); rp = "/".equals(rp) ? "" : rp; final Representation nrep; if (context.isFullHalMode()) { nrep = new Representation(rp + "/" + id.getValue()); } else { nrep = new Representation(); } nrep.addProperties(d); if (id.getValue().endsWith(RequestContext.FS_FILES_SUFFIX)) { if (context.isFullHalMode()) { CollectionRepresentationFactory.addSpecialProperties( nrep, TYPE.FILES_BUCKET, d); } rep.addRepresentation("rh:bucket", nrep); } else if (RequestContext._SCHEMAS.equals(id)) { if (context.isFullHalMode()) { CollectionRepresentationFactory.addSpecialProperties( nrep, TYPE.SCHEMA_STORE, d); } rep.addRepresentation("rh:schema-store", nrep); } else { if (context.isFullHalMode()) { CollectionRepresentationFactory.addSpecialProperties( nrep, TYPE.COLLECTION, d); } rep.addRepresentation("rh:coll", nrep); } } else { // this shoudn't be possible LOGGER.error("Collection missing string _id field: {}", _id); } }); } }