/* * 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.document; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import java.time.Instant; import java.util.List; import org.bson.BsonDocument; import org.bson.BsonString; import org.bson.BsonValue; import org.bson.types.ObjectId; import org.restheart.Configuration; import org.restheart.hal.Link; import org.restheart.hal.Representation; import static org.restheart.hal.Representation.HAL_JSON_MEDIA_TYPE; import org.restheart.hal.UnsupportedDocumentIdException; import org.restheart.handlers.metadata.InvalidMetadataException; import org.restheart.metadata.Relationship; import org.restheart.handlers.IllegalQueryParamenterException; import org.restheart.handlers.RequestContext; import org.restheart.handlers.RequestContext.TYPE; import org.restheart.utils.URLUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Andrea Di Cesare {@literal <andrea@softinstigate.com>} */ public class DocumentRepresentationFactory { public DocumentRepresentationFactory() { } private static final Logger LOGGER = LoggerFactory.getLogger(DocumentRepresentationFactory.class); /** * * @param href * @param exchange * @param context * @param data * @return * @throws IllegalQueryParamenterException */ public Representation getRepresentation(String href, HttpServerExchange exchange, RequestContext context, BsonDocument data) throws IllegalQueryParamenterException { Representation rep; BsonValue id = data.get("_id"); String _docIdType = null; if (context.isFullHalMode()) { rep = new Representation(URLUtils.getReferenceLink(context, URLUtils.getParentPath(href), id)); } else { rep = new Representation(); } data.keySet() .stream().forEach((key) -> rep.addProperty(key, data.get(key))); addRelationshipsLinks(rep, context, data); // link templates and curies String requestPath = URLUtils.removeTrailingSlashes(exchange.getRequestPath()); String parentPath; // the document (file) representation can be asked for requests to collection (bucket) boolean isEmbedded = TYPE.COLLECTION.equals(context.getType()) || TYPE.FILES_BUCKET.equals(context.getType()) || TYPE.SCHEMA_STORE.equals(context.getType()); if (isEmbedded) { parentPath = requestPath; } else { parentPath = URLUtils.getParentPath(requestPath); } if (isBinaryFile(data)) { if (_docIdType == null) { rep.addLink(new Link("rh:data", String.format("%s/%s", href, RequestContext.BINARY_CONTENT))); } else { rep.addLink(new Link("rh:data", String.format("%s/%s?%s", href, RequestContext.BINARY_CONTENT, _docIdType))); } } // link templates if (!isEmbedded && context.isFullHalMode()) { addSpecialProperties(rep, context.getType(), data); if (isBinaryFile(data)) { if (context.isParentAccessible()) { rep.addLink(new Link("rh:bucket", parentPath)); } rep.addLink(new Link("rh:file", parentPath + "/{fileid}{?id_type}", true)); } else { if (context.isParentAccessible()) { rep.addLink(new Link("rh:coll", parentPath)); } rep.addLink(new Link("rh:document", parentPath + "/{docid}{?id_type}", true)); } if (!isEmbedded) { rep.addLink(new Link("rh", "curies", Configuration.RESTHEART_ONLINE_DOC_URL + "/{rel}.html", true), true); } } return rep; } private static boolean isBinaryFile(BsonDocument data) { return data.containsKey("filename") && data.containsKey("chunkSize"); } 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())); } } Object id = data.get("_id"); // generate the _created_on timestamp from the _id if this is an instance of ObjectId if (data.get("_created_on") == null && id != null && id instanceof ObjectId) { rep.addProperty("_created_on", new BsonString(Instant.ofEpochSecond(((ObjectId) id).getTimestamp()).toString())); } } private static void addRelationshipsLinks(Representation rep, RequestContext context, BsonDocument data) { List<Relationship> rels = null; try { rels = Relationship.getFromJson(context.getCollectionProps()); } catch (InvalidMetadataException ex) { rep.addWarning("collection " + context.getDBName() + "/" + context.getCollectionName() + " has invalid relationships definition"); } if (rels != null) { for (Relationship rel : rels) { try { String link = rel.getRelationshipLink(context, context.getDBName(), context.getCollectionName(), data); if (link != null) { rep.addLink(new Link(rel.getRel(), link)); } } catch (IllegalArgumentException | UnsupportedDocumentIdException ex) { rep.addWarning(ex.getMessage()); LOGGER.debug(ex.getMessage()); } } } } }