From f2bffa9cddf814417365d231974c805e1523795c Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Fri, 6 Feb 2015 18:59:26 +0100 Subject: [PATCH 01/17] set version back to 0.10.0-SNAPSHOT in develop --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3835325a17..77fc6f4765 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.restheart restheart - 0.10.0.CR + 0.10.0-SNAPSHOT jar Restheart From 4aaaa9141f51c250013e8d604be0e6ce71a6efb4 Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Fri, 6 Feb 2015 19:28:55 +0100 Subject: [PATCH 02/17] removed unused dependency from pom --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index 77fc6f4765..2b27eccbed 100644 --- a/pom.xml +++ b/pom.xml @@ -122,12 +122,6 @@ 1.9 - - org.infinispan - infinispan-core - 7.0.3.Final - - junit junit From 6e0437d43b84ab2f961a4598498f412c009ff7d2 Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Sat, 7 Feb 2015 16:14:33 +0100 Subject: [PATCH 03/17] added workaroung for undertow bug 377 (leading to get always not authorized with security predicates involving regex or path-template predicates) cleaned up the security.yml file and added comments on example predicates --- etc/restheart-dev.yml | 12 +++++----- etc/security.yml | 22 ++++++++++++++----- nbactions.xml | 6 ++--- .../security/impl/SimpleAccessManager.java | 10 ++++++++- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/etc/restheart-dev.yml b/etc/restheart-dev.yml index f1cec8af9e..51e70916fe 100644 --- a/etc/restheart-dev.yml +++ b/etc/restheart-dev.yml @@ -109,12 +109,12 @@ application-logic-mounts: # the provided default implementations of IDM and AM are SimpleFileIdentityManager and SimpleAccessManager. # conf-file paths are either absolute (starting with /) or relative to the restheart.jar directory -#idm: -# implementation-class: org.restheart.security.impl.SimpleFileIdentityManager -# conf-file: ../etc/security.yml -#access-manager: -# implementation-class: org.restheart.security.impl.SimpleAccessManager -# conf-file: ../etc/security.yml +idm: + implementation-class: org.restheart.security.impl.SimpleFileIdentityManager + conf-file: ../etc/security.yml +access-manager: + implementation-class: org.restheart.security.impl.SimpleAccessManager + conf-file: ../etc/security.yml # authentication token diff --git a/etc/security.yml b/etc/security.yml index 12bc50320e..936d8675d4 100644 --- a/etc/security.yml +++ b/etc/security.yml @@ -25,17 +25,27 @@ users: roles: [users, admins] permissions: +# users with role 'admins' can do anything - role: admins predicate: path-prefix[path="/"] - + +# not authenticated user can only GET any resource under the /publicdb URI - role: $unauthenticated predicate: path-prefix[path="/publicdb/"] and method[value="GET"] - - - role: $unauthenticated - predicate: path[path="/integrationtestdb/coll1"] and method[value="GET"] - + +# users with role 'users' can GET any collection or document resource (excluding dbs) - role: users predicate: regex[pattern="/.*/.*", value="%R", full-match=true] and method[value="GET"] +# users with role 'users' can do anything on the collection /publicdb/{username} + - role: users + predicate: path-template[value="/publicdb/{username}"] and equals[%u, "${username}"] + +# users with role 'users' can do anything on documents of the collection /publicdb/{username} - role: users - predicate: path-prefix[path="/publicdb/{username}/"] \ No newline at end of file + predicate: path-template[value="/publicdb/{username}/{doc}"] and equals[%u, "${username}"] + +# same than previous one, but using regex predicate +# users with role 'users' can do anything on documents of the collection /publicdb/{username} +# - role: users +# predicate: regex[pattern="/publicdb/(.*?)/.*", value="%R", full-match=true] and equals[%u, "${1}"] \ No newline at end of file diff --git a/nbactions.xml b/nbactions.xml index 230bec77b1..41e417be1c 100644 --- a/nbactions.xml +++ b/nbactions.xml @@ -10,7 +10,7 @@ org.codehaus.mojo:exec-maven-plugin:1.2.1:exec - -Xdock:name=RESTHeart -classpath %classpath org.restheart.Bootstrapper etc/restheart-integrationtest.yml + -Xdock:name=RESTHeart -classpath %classpath org.restheart.Bootstrapper etc/restheart-dev.yml java @@ -24,7 +24,7 @@ org.codehaus.mojo:exec-maven-plugin:1.2.1:exec - -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=${jpda.address} -Xdock:name=RESTHeart -classpath %classpath org.restheart.Bootstrapper etc/restheart-integrationtest.yml + -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=${jpda.address} -Xdock:name=RESTHeart -classpath %classpath org.restheart.Bootstrapper etc/restheart-dev.yml java true @@ -39,7 +39,7 @@ org.codehaus.mojo:exec-maven-plugin:1.2.1:exec - -Xdock:name=RESTHeart -classpath %classpath org.restheart.Bootstrapper etc/restheart-integrationtest.yml + -Xdock:name=RESTHeart -classpath %classpath org.restheart.Bootstrapper etc/restheart-dev.yml java diff --git a/src/main/java/org/restheart/security/impl/SimpleAccessManager.java b/src/main/java/org/restheart/security/impl/SimpleAccessManager.java index 517ee59b75..ab5d16a053 100644 --- a/src/main/java/org/restheart/security/impl/SimpleAccessManager.java +++ b/src/main/java/org/restheart/security/impl/SimpleAccessManager.java @@ -31,6 +31,8 @@ import java.util.stream.Stream; import static com.google.common.collect.Sets.newHashSet; +import static io.undertow.predicate.Predicate.PREDICATE_CONTEXT; +import java.util.TreeMap; import java.util.function.Consumer; /** @@ -68,7 +70,7 @@ Consumer> consumeConfiguration() { try { predicate = PredicateParser.parse((String) _predicate, this.getClass().getClassLoader()); } catch (Throwable t) { - throw new IllegalArgumentException("wrong configuration file format. wrong predictate" + (String) _predicate, t); + throw new IllegalArgumentException("wrong configuration file format. wrong predicate " + (String) _predicate, t); } aclForRole(role).add(predicate); @@ -86,6 +88,12 @@ public boolean isAllowed(HttpServerExchange exchange, RequestContext context) { return false; } + // this fixes undertow bug 377 + // https://issues.jboss.org/browse/UNDERTOW-377 + if (exchange.getAttachment(PREDICATE_CONTEXT) == null) { + exchange.putAttachment(PREDICATE_CONTEXT, new TreeMap()); + } + return roles(exchange).anyMatch(role -> aclForRole(role).stream().anyMatch(p -> p.resolve(exchange))); } From e661005caf1e52465d5a259e0ad31956d34f0bf2 Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Sun, 8 Feb 2015 18:36:47 +0100 Subject: [PATCH 04/17] updated mongodb java driver to version 2.13.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2b27eccbed..5d8609a139 100644 --- a/pom.xml +++ b/pom.xml @@ -89,7 +89,7 @@ org.mongodb mongo-java-driver - 2.12.4 + 2.13.0 From c96bc9844f13d6f3f03b682b79f2b16fd9d2d00b Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Sun, 8 Feb 2015 18:39:41 +0100 Subject: [PATCH 05/17] RESTHeart now uses the strict mode representations of BSON types (RH-63) removed support for obsolete detect_oids query parameter --- .../java/org/restheart/db/CollectionDAO.java | 7 +--- src/main/java/org/restheart/db/Database.java | 2 +- src/main/java/org/restheart/db/DbsDAO.java | 4 +- src/main/java/org/restheart/hal/HALUtils.java | 42 ------------------- .../org/restheart/hal/Representation.java | 16 +++---- .../restheart/handlers/RequestContext.java | 16 ------- .../CollectionRepresentationFactory.java | 3 -- .../collection/GetCollectionHandler.java | 2 +- .../database/DBRepresentationFactory.java | 4 -- .../injectors/BodyInjectorHandler.java | 24 ----------- .../RequestContextInjectorHandler.java | 17 -------- .../root/RootRepresentationFactory.java | 3 -- .../restheart/test/performance/LoadGetPT.java | 2 +- 13 files changed, 11 insertions(+), 131 deletions(-) diff --git a/src/main/java/org/restheart/db/CollectionDAO.java b/src/main/java/org/restheart/db/CollectionDAO.java index 6c946831c4..bbc45fc13c 100644 --- a/src/main/java/org/restheart/db/CollectionDAO.java +++ b/src/main/java/org/restheart/db/CollectionDAO.java @@ -186,16 +186,11 @@ ArrayList getCollectionData( int pagesize, Deque sortBy, Deque filters, - DBCursorPool.EAGER_CURSOR_ALLOCATION_POLICY eager, - boolean detectOids) throws JSONParseException { + DBCursorPool.EAGER_CURSOR_ALLOCATION_POLICY eager) throws JSONParseException { ArrayList ret = new ArrayList<>(); int toskip = pagesize * (page - 1); - if (detectOids) { - filters = replaceObjectIdsInFilters(filters); - } - DBCursor cursor; SkippedDBCursor _cursor = null; diff --git a/src/main/java/org/restheart/db/Database.java b/src/main/java/org/restheart/db/Database.java index 1aea240d6d..0e88d282a6 100644 --- a/src/main/java/org/restheart/db/Database.java +++ b/src/main/java/org/restheart/db/Database.java @@ -83,7 +83,7 @@ public interface Database { * @param detectObjectids * @return Collection Data as ArrayList of DBObject */ - ArrayList getCollectionData(DBCollection collection, int page, int pagesize, Deque sortBy, Deque filter, DBCursorPool.EAGER_CURSOR_ALLOCATION_POLICY cursorAllocationPolicy, boolean detectObjectids); + ArrayList getCollectionData(DBCollection collection, int page, int pagesize, Deque sortBy, Deque filter, DBCursorPool.EAGER_CURSOR_ALLOCATION_POLICY cursorAllocationPolicy); /** * diff --git a/src/main/java/org/restheart/db/DbsDAO.java b/src/main/java/org/restheart/db/DbsDAO.java index e309d05158..e1e0bd843c 100644 --- a/src/main/java/org/restheart/db/DbsDAO.java +++ b/src/main/java/org/restheart/db/DbsDAO.java @@ -375,8 +375,8 @@ public long getCollectionSize(DBCollection coll, Deque filters) { } @Override - public ArrayList getCollectionData(DBCollection coll, int page, int pagesize, Deque sortBy, Deque filter, DBCursorPool.EAGER_CURSOR_ALLOCATION_POLICY cursorAllocationPolicy, boolean detectObjectids) { - return collectionDAO.getCollectionData(coll, page, pagesize, sortBy, filter, cursorAllocationPolicy, detectObjectids); + public ArrayList getCollectionData(DBCollection coll, int page, int pagesize, Deque sortBy, Deque filter, DBCursorPool.EAGER_CURSOR_ALLOCATION_POLICY cursorAllocationPolicy) { + return collectionDAO.getCollectionData(coll, page, pagesize, sortBy, filter, cursorAllocationPolicy); } @Override diff --git a/src/main/java/org/restheart/hal/HALUtils.java b/src/main/java/org/restheart/hal/HALUtils.java index cb6ee14de3..e32cd44b09 100644 --- a/src/main/java/org/restheart/hal/HALUtils.java +++ b/src/main/java/org/restheart/hal/HALUtils.java @@ -30,48 +30,6 @@ * @author Andrea Di Cesare */ public class HALUtils { - /** - * this replaces string that are valid ObjectIds with ObjectIds objects. - * - * @param source - */ - public static void replaceStringsWithObjectIds(BSONObject source) { - if (source == null) { - return; - } - - source.keySet().stream().forEach((key) -> { - Object value = source.get(key); - - if (value instanceof BSONObject) { - replaceStringsWithObjectIds((BSONObject) value); - } else if (ObjectId.isValid(value.toString())) { - source.put(key, new ObjectId(value.toString())); - } - }); - } - - /** - * this replaces ObjectIds with Strings - * - * @param source - */ - public static void replaceObjectIdsWithStrings(BSONObject source) { - if (source == null) { - return; - } - - source.keySet().stream().forEach((key) -> { - Object value = source.get(key); - - if (value instanceof BSONObject) { - replaceObjectIdsWithStrings((BSONObject) value); - } else if (value instanceof ObjectId) { - source.put(key, value.toString()); - } - }); - } - /** * * @param exchange diff --git a/src/main/java/org/restheart/hal/Representation.java b/src/main/java/org/restheart/hal/Representation.java index 240bb17e63..25a0c24782 100644 --- a/src/main/java/org/restheart/hal/Representation.java +++ b/src/main/java/org/restheart/hal/Representation.java @@ -20,6 +20,8 @@ import com.mongodb.BasicDBList; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import com.mongodb.util.JSONSerializers; +import com.mongodb.util.ObjectSerializer; import java.util.Objects; import org.bson.BSONObject; import org.bson.types.ObjectId; @@ -29,6 +31,7 @@ * @author Andrea Di Cesare */ public class Representation { + private final ObjectSerializer serializer = JSONSerializers.getStrict(); /** * Supported content types @@ -100,14 +103,7 @@ public void addLink(Link link, boolean inArray) { * @param value */ public void addProperty(String key, Object value) { - if (value instanceof ObjectId) { - properties.append(key, value.toString()); - } else if (value instanceof BSONObject) { - HALUtils.replaceObjectIdsWithStrings((BSONObject) value); - properties.append(key, value); - } else { - properties.append(key, value); - } + properties.append(key, value); } /** @@ -119,8 +115,6 @@ public void addProperties(DBObject props) { return; } - HALUtils.replaceObjectIdsWithStrings(props); - properties.putAll(props); } @@ -149,7 +143,7 @@ public void addWarning(String warning) { @Override public String toString() { - return getDBObject().toString(); + return serializer.serialize(getDBObject()); } @Override diff --git a/src/main/java/org/restheart/handlers/RequestContext.java b/src/main/java/org/restheart/handlers/RequestContext.java index cfea0fb857..e4bc40af69 100644 --- a/src/main/java/org/restheart/handlers/RequestContext.java +++ b/src/main/java/org/restheart/handlers/RequestContext.java @@ -72,8 +72,6 @@ public enum DOC_ID_TYPE { public static final String FILTER_QPARAM_KEY = "filter"; public static final String EAGER_CURSOR_ALLOCATION_POLICY_QPARAM_KEY = "eager"; public static final String DOC_ID_TYPE_KEY = "doc_id_type"; - public static final String DETECT_OBJECTIDS_KEY = "detect_oids"; - public static final String SLASH = "/"; public static final String PATCH = "PATCH"; public static final String UNDERSCORE = "_"; @@ -573,20 +571,6 @@ public void setDocIdType(DOC_ID_TYPE docIdType) { this.docIdType = docIdType; } - /** - * @return the detectObjectIds - */ - public boolean isDetectObjectIds() { - return detectObjectIds; - } - - /** - * @param detectObjectIds the detectObjectIds to set - */ - public void setDetectObjectIds(boolean detectObjectIds) { - this.detectObjectIds = detectObjectIds; - } - /** * @param documentId the documentId to set */ diff --git a/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java b/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java index 26fed41e6f..d88953f5f2 100644 --- a/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java +++ b/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java @@ -108,9 +108,6 @@ private void embeddedDocuments(List embeddedData, String requestPath, nrep.addProperty("_type", RequestContext.TYPE.DOCUMENT.name()); - if (d.get("_etag") != null && d.get("_etag") instanceof ObjectId) { - d.put("_etag", ((ObjectId) d.get("_etag")).toString()); // represent the etag as a string - } rep.addRepresentation("rh:doc", nrep); } } diff --git a/src/main/java/org/restheart/handlers/collection/GetCollectionHandler.java b/src/main/java/org/restheart/handlers/collection/GetCollectionHandler.java index 6faabb0a8a..0418a5c49f 100644 --- a/src/main/java/org/restheart/handlers/collection/GetCollectionHandler.java +++ b/src/main/java/org/restheart/handlers/collection/GetCollectionHandler.java @@ -70,7 +70,7 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t try { data = getDatabase().getCollectionData(coll, context.getPage(), context.getPagesize(), - context.getSortBy(), context.getFilter(), context.getCursorAllocationPolicy(), context.isDetectObjectIds()); + context.getSortBy(), context.getFilter(), context.getCursorAllocationPolicy()); } catch (JSONParseException jpe) { // the filter expression is not a valid json string LOGGER.error("invalid filter expression {}", context.getFilter(), jpe); diff --git a/src/main/java/org/restheart/handlers/database/DBRepresentationFactory.java b/src/main/java/org/restheart/handlers/database/DBRepresentationFactory.java index 7c5439c26b..3d321aeea0 100644 --- a/src/main/java/org/restheart/handlers/database/DBRepresentationFactory.java +++ b/src/main/java/org/restheart/handlers/database/DBRepresentationFactory.java @@ -102,10 +102,6 @@ private void embeddedCollections( nrep.addProperty("_type", RequestContext.TYPE.COLLECTION.name()); - if (d.get("_etag") != null && d.get("_etag") instanceof ObjectId) { - d.put("_etag", ((ObjectId) d.get("_etag")).toString()); // represent the etag as a string - } - nrep.addProperties(d); rep.addRepresentation("rh:coll", nrep); diff --git a/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java b/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java index 785c97aabc..85bc49c2ba 100644 --- a/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java +++ b/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java @@ -115,33 +115,9 @@ private static boolean isNotFormData(HeaderValues contentTypes) { */ private void filterJsonContent(DBObject content, RequestContext ctx) { filterOutReservedKeys(content, ctx); - replaceStringWithObjectId(ctx, content); ctx.setContent(content); } - /** - * replace string that are valid ObjectIds with ObjectIds objects - * - * @param context - * @param content - */ - private void replaceStringWithObjectId(RequestContext context, DBObject content) { - if (context.isDetectObjectIds()) { - Object keepId = null; - - // if detect_oids==true and doc_id_type==string, replace all objectids but the id - if (context.getDocIdType() == RequestContext.DOC_ID_TYPE.STRING) { - keepId = content.removeField("_id"); - } - - HALUtils.replaceStringsWithObjectIds(content); - - if (keepId != null) { - content.put("_id", keepId); - } - } - } - /** * Filter out reserved keys, removoing them from request * diff --git a/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java b/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java index 7078163ed0..2a69873c1e 100644 --- a/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java +++ b/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java @@ -32,7 +32,6 @@ import io.undertow.server.HttpServerExchange; import java.util.Deque; import org.bson.BSONObject; -import static org.restheart.handlers.RequestContext.DETECT_OBJECTIDS_KEY; import org.restheart.handlers.RequestContext.DOC_ID_TYPE; import static org.restheart.handlers.RequestContext.DOC_ID_TYPE_KEY; import org.restheart.utils.UnsupportedDocumentIdException; @@ -229,22 +228,6 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t return; } - // get the autodetect objectid parameter - Deque __autodetectObjectId = exchange.getQueryParameters().get(DETECT_OBJECTIDS_KEY); - - // default value - boolean detectObjectIds = true; - - if (__autodetectObjectId != null && !__autodetectObjectId.isEmpty()) { - String _autodetectObjectId = __autodetectObjectId.getFirst(); - - if (_autodetectObjectId != null && !_autodetectObjectId.isEmpty()) { - detectObjectIds = "true".equalsIgnoreCase(_autodetectObjectId); - } - } - - rcontext.setDetectObjectIds(detectObjectIds); - getNext().handleRequest(exchange, rcontext); } diff --git a/src/main/java/org/restheart/handlers/root/RootRepresentationFactory.java b/src/main/java/org/restheart/handlers/root/RootRepresentationFactory.java index de379c1457..c4f50b3505 100644 --- a/src/main/java/org/restheart/handlers/root/RootRepresentationFactory.java +++ b/src/main/java/org/restheart/handlers/root/RootRepresentationFactory.java @@ -88,9 +88,6 @@ private void embeddedDocuments(List embeddedData, boolean trailingSlas nrep.addProperty("_type", RequestContext.TYPE.DB.name()); - if (d.get("_etag") != null && d.get("_etag") instanceof ObjectId) { - d.put("_etag", ((ObjectId) d.get("_etag")).toString()); // represent the etag as a string - } nrep.addProperties(d); rep.addRepresentation("rh:db", nrep); diff --git a/src/test/java/org/restheart/test/performance/LoadGetPT.java b/src/test/java/org/restheart/test/performance/LoadGetPT.java index 7967e0e9fe..6b3df660ff 100644 --- a/src/test/java/org/restheart/test/performance/LoadGetPT.java +++ b/src/test/java/org/restheart/test/performance/LoadGetPT.java @@ -160,7 +160,7 @@ public void dbdirect() { ArrayList data; try { - data = new DbsDAO().getCollectionData(dbcoll, page, pagesize, null, _filter, DBCursorPool.EAGER_CURSOR_ALLOCATION_POLICY.NONE, true); + data = new DbsDAO().getCollectionData(dbcoll, page, pagesize, null, _filter, DBCursorPool.EAGER_CURSOR_ALLOCATION_POLICY.NONE); } catch(Exception e) { System.out.println("error: " + e.getMessage()); return; From 8e84737d33f4a0c685affb6f8d1fa288f5dde773 Mon Sep 17 00:00:00 2001 From: Maurizio Turatti Date: Mon, 9 Feb 2015 09:21:44 +0100 Subject: [PATCH 06/17] Add GetBinaryFileHandlerIT --- .../handlers/files/GetFileHandler.java | 8 +++ .../files/GetBinaryFileHandlerIT.java | 64 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerIT.java diff --git a/src/main/java/org/restheart/handlers/files/GetFileHandler.java b/src/main/java/org/restheart/handlers/files/GetFileHandler.java index 3381b45786..2f9346067d 100644 --- a/src/main/java/org/restheart/handlers/files/GetFileHandler.java +++ b/src/main/java/org/restheart/handlers/files/GetFileHandler.java @@ -43,6 +43,10 @@ public class GetFileHandler extends PipedHttpHandler { private static final Logger LOGGER = LoggerFactory.getLogger(GetFileHandler.class); + public GetBinaryFileHandler() { + super(); + } + @Override public void handleRequest(HttpServerExchange exchange, RequestContext context) throws Exception { LOGGER.debug("GET " + exchange.getRequestURL()); @@ -108,4 +112,8 @@ private String extractFilename(final GridFSDBFile dbsfile) { static String extractBucketName(final String collectionName) { return collectionName.split("\\.")[0]; } + + GetBinaryFileHandler(Object object, Object object0) { + super(null, null); + } } diff --git a/src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerIT.java b/src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerIT.java new file mode 100644 index 0000000000..4110da1b3c --- /dev/null +++ b/src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerIT.java @@ -0,0 +1,64 @@ +/* + * RESTHeart - the data REST API server + * 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 . + */ +package org.restheart.handlers.files; + +import com.mongodb.MongoClient; +import java.io.File; +import java.nio.file.Path; +import org.apache.http.HttpHost; +import org.apache.http.client.fluent.Executor; +import org.apache.http.client.fluent.Request; +import org.apache.http.client.fluent.Response; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.restheart.Configuration; +import org.restheart.db.MongoDBClientSingleton; + +/** + * + * @author Maurizio Turatti + */ +public class GetBinaryFileHandlerIT { + + public static final String CONFIG_YML = "etc/restheart-integrationtest.yml"; + public static final String HOST = "localhost"; + + static Executor executor = null; + static MongoClient mongoClient = null; + + @BeforeClass + public static void setUpClass() throws Exception { + Path confFilePath = new File(CONFIG_YML).toPath(); + Configuration conf = new Configuration(confFilePath); + MongoDBClientSingleton.init(conf); + + mongoClient = MongoDBClientSingleton.getInstance().getClient(); + executor = Executor.newInstance().authPreemptive(new HttpHost(HOST, 18080, "HTTP")).auth(new HttpHost(HOST), "admin", "changeit"); + } + + public GetBinaryFileHandlerIT() { + } + + @Test + public void testHandleRequest() throws Exception { + Response resp = executor.execute(Request.Get("http://localhost:18080/filedb/mybucket.files/54c90079300432cf132a6849")); + Assert.assertEquals(200, resp.returnResponse().getStatusLine().getStatusCode()); + } + +} From 9ce4e785bfd4a80026a7a5f7768fa69f09c47073 Mon Sep 17 00:00:00 2001 From: Maurizio Turatti Date: Mon, 9 Feb 2015 15:01:07 +0100 Subject: [PATCH 07/17] Test file GET with real content --- .../handlers/files/GetFileHandler.java | 4 +- .../files/GetBinaryFileHandlerIT.java | 64 ------------ .../handlers/files/GetFileHandlerIT.java | 98 +++++++++++++++++++ ...ndlerTest.java => GetFileHandlerTest.java} | 4 +- 4 files changed, 102 insertions(+), 68 deletions(-) delete mode 100644 src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerIT.java create mode 100644 src/test/java/org/restheart/handlers/files/GetFileHandlerIT.java rename src/test/java/org/restheart/handlers/files/{GetBinaryFileHandlerTest.java => GetFileHandlerTest.java} (93%) diff --git a/src/main/java/org/restheart/handlers/files/GetFileHandler.java b/src/main/java/org/restheart/handlers/files/GetFileHandler.java index 2f9346067d..22e406185e 100644 --- a/src/main/java/org/restheart/handlers/files/GetFileHandler.java +++ b/src/main/java/org/restheart/handlers/files/GetFileHandler.java @@ -43,7 +43,7 @@ public class GetFileHandler extends PipedHttpHandler { private static final Logger LOGGER = LoggerFactory.getLogger(GetFileHandler.class); - public GetBinaryFileHandler() { + public GetFileHandler() { super(); } @@ -113,7 +113,7 @@ static String extractBucketName(final String collectionName) { return collectionName.split("\\.")[0]; } - GetBinaryFileHandler(Object object, Object object0) { + GetFileHandler(Object object, Object object0) { super(null, null); } } diff --git a/src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerIT.java b/src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerIT.java deleted file mode 100644 index 4110da1b3c..0000000000 --- a/src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerIT.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * RESTHeart - the data REST API server - * 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 . - */ -package org.restheart.handlers.files; - -import com.mongodb.MongoClient; -import java.io.File; -import java.nio.file.Path; -import org.apache.http.HttpHost; -import org.apache.http.client.fluent.Executor; -import org.apache.http.client.fluent.Request; -import org.apache.http.client.fluent.Response; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.restheart.Configuration; -import org.restheart.db.MongoDBClientSingleton; - -/** - * - * @author Maurizio Turatti - */ -public class GetBinaryFileHandlerIT { - - public static final String CONFIG_YML = "etc/restheart-integrationtest.yml"; - public static final String HOST = "localhost"; - - static Executor executor = null; - static MongoClient mongoClient = null; - - @BeforeClass - public static void setUpClass() throws Exception { - Path confFilePath = new File(CONFIG_YML).toPath(); - Configuration conf = new Configuration(confFilePath); - MongoDBClientSingleton.init(conf); - - mongoClient = MongoDBClientSingleton.getInstance().getClient(); - executor = Executor.newInstance().authPreemptive(new HttpHost(HOST, 18080, "HTTP")).auth(new HttpHost(HOST), "admin", "changeit"); - } - - public GetBinaryFileHandlerIT() { - } - - @Test - public void testHandleRequest() throws Exception { - Response resp = executor.execute(Request.Get("http://localhost:18080/filedb/mybucket.files/54c90079300432cf132a6849")); - Assert.assertEquals(200, resp.returnResponse().getStatusLine().getStatusCode()); - } - -} diff --git a/src/test/java/org/restheart/handlers/files/GetFileHandlerIT.java b/src/test/java/org/restheart/handlers/files/GetFileHandlerIT.java new file mode 100644 index 0000000000..e9178f3a43 --- /dev/null +++ b/src/test/java/org/restheart/handlers/files/GetFileHandlerIT.java @@ -0,0 +1,98 @@ +/* + * RESTHeart - the data REST API server + * 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 . + */ +package org.restheart.handlers.files; + +import com.mongodb.DB; +import com.mongodb.Mongo; +import com.mongodb.MongoClient; +import com.mongodb.gridfs.GridFS; +import com.mongodb.gridfs.GridFSInputFile; +import java.io.File; +import java.io.InputStream; +import java.net.UnknownHostException; +import org.apache.http.HttpHost; +import org.apache.http.client.fluent.Executor; +import org.apache.http.client.fluent.Request; +import org.apache.http.client.fluent.Response; +import org.junit.AfterClass; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +/** + * + * @author Maurizio Turatti + */ +public class GetFileHandlerIT { + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + public static final String HOST = "localhost"; + public static final String FILENAME = "RESTHeart_documentation.pdf"; + public static final String DB_NAME = "testdb-" + System.currentTimeMillis(); + public static final String DB_URL = "http://localhost:18080/" + DB_NAME; + public static final String BUCKET = "mybucket"; + public static Object OID; + static Executor executor = null; + + @BeforeClass + public static void setUpClass() throws UnknownHostException { + DB db = getDatabase(); + InputStream is = GetFileHandlerIT.class.getResourceAsStream("/" + FILENAME); + GridFS gridfs = new GridFS(db, BUCKET); + GridFSInputFile gfsFile = gridfs.createFile(is); + OID = gfsFile.getId(); + gfsFile.setFilename(FILENAME); + gfsFile.save(); + + executor = Executor.newInstance() + .authPreemptive(new HttpHost(HOST, 18080, "HTTP")) + .auth(new HttpHost(HOST), "admin", "changeit"); + } + + @AfterClass + public static void afterClass() throws UnknownHostException { + DB db = getDatabase(); + db.dropDatabase(); + } + + public GetFileHandlerIT() { + } + + @Test + public void testHandleRequest() throws Exception { + System.out.println("testHandleRequest"); + String url = DB_URL + "/" + BUCKET + ".files/" + OID + "/binary"; + System.out.println("URL = " + url); + Response resp = executor.execute(Request.Get(url)); + File tempFile = tempFolder.newFile(FILENAME); + resp.saveContent(tempFile); + assertTrue(tempFile.length() > 0); + } + + private static DB getDatabase() throws UnknownHostException { + Mongo mongo = new MongoClient(); + DB db = mongo.getDB(DB_NAME); + return db; + } + +} diff --git a/src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerTest.java b/src/test/java/org/restheart/handlers/files/GetFileHandlerTest.java similarity index 93% rename from src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerTest.java rename to src/test/java/org/restheart/handlers/files/GetFileHandlerTest.java index a32f5aaa0c..e99b52aa84 100644 --- a/src/test/java/org/restheart/handlers/files/GetBinaryFileHandlerTest.java +++ b/src/test/java/org/restheart/handlers/files/GetFileHandlerTest.java @@ -24,9 +24,9 @@ * * @author Maurizio Turatti */ -public class GetBinaryFileHandlerTest { +public class GetFileHandlerTest { - public GetBinaryFileHandlerTest() { + public GetFileHandlerTest() { } @Test From a629057fbe524add2d3e9384faf878274952a50f Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Tue, 10 Feb 2015 19:29:18 +0100 Subject: [PATCH 08/17] updated test cases to support the new strict mode json representation --- .../handlers/injectors/BodyInjectorHandler.java | 2 +- .../test/integration/DeleteCollectionIT.java | 2 +- .../org/restheart/test/integration/DeleteDBIT.java | 2 +- .../restheart/test/integration/DeleteDocumentIT.java | 2 +- .../org/restheart/test/integration/DocIdTypeIT.java | 11 +++++------ .../restheart/test/integration/PatchCollectionIT.java | 4 ++-- .../org/restheart/test/integration/PatchDBIT.java | 4 ++-- .../restheart/test/integration/PatchDocumentIT.java | 2 +- .../restheart/test/integration/PostCollectionIT.java | 10 +++++----- .../restheart/test/integration/PutCollectionIT.java | 2 +- .../java/org/restheart/test/integration/PutDBIT.java | 2 +- .../org/restheart/test/integration/PutDocumentIT.java | 2 +- 12 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java b/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java index 85bc49c2ba..106a33a68c 100644 --- a/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java +++ b/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java @@ -81,7 +81,7 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t DBObject content = null; try { content = (DBObject) JSON.parse(contentString); - } catch (JSONParseException ex) { + } catch (JSONParseException | IllegalArgumentException ex) { ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_NOT_ACCEPTABLE, "Invalid data", ex); return; } diff --git a/src/test/java/org/restheart/test/integration/DeleteCollectionIT.java b/src/test/java/org/restheart/test/integration/DeleteCollectionIT.java index a11679cc59..49713fb11b 100644 --- a/src/test/java/org/restheart/test/integration/DeleteCollectionIT.java +++ b/src/test/java/org/restheart/test/integration/DeleteCollectionIT.java @@ -61,7 +61,7 @@ public void testDeleteCollection() throws Exception { JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - String etag = content.get("_etag").asString(); + String etag = content.get("_etag").asObject().get("$oid").asString(); // try to delete with correct etag resp = adminExecutor.execute(Request.Delete(collectionTmpUri).addHeader(Headers.IF_MATCH_STRING, etag)); diff --git a/src/test/java/org/restheart/test/integration/DeleteDBIT.java b/src/test/java/org/restheart/test/integration/DeleteDBIT.java index 4cca6d6375..1fe3e4f4ac 100644 --- a/src/test/java/org/restheart/test/integration/DeleteDBIT.java +++ b/src/test/java/org/restheart/test/integration/DeleteDBIT.java @@ -57,7 +57,7 @@ public void testDeleteDB() throws Exception { JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - String etag = content.get("_etag").asString(); + String etag = content.get("_etag").asObject().get("$oid").asString(); // try to delete with correct etag resp = adminExecutor.execute(Request.Delete(dbTmpUri).addHeader(Headers.IF_MATCH_STRING, etag)); diff --git a/src/test/java/org/restheart/test/integration/DeleteDocumentIT.java b/src/test/java/org/restheart/test/integration/DeleteDocumentIT.java index 97044beb2c..5235e844db 100644 --- a/src/test/java/org/restheart/test/integration/DeleteDocumentIT.java +++ b/src/test/java/org/restheart/test/integration/DeleteDocumentIT.java @@ -65,7 +65,7 @@ public void testDeleteDocument() throws Exception { JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - String etag = content.get("_etag").asString(); + String etag = content.get("_etag").asObject().get("$oid").asString(); // try to delete with correct etag resp = adminExecutor.execute(Request.Delete(documentTmpUri).addHeader(Headers.IF_MATCH_STRING, etag)); diff --git a/src/test/java/org/restheart/test/integration/DocIdTypeIT.java b/src/test/java/org/restheart/test/integration/DocIdTypeIT.java index 51363b7b01..584de22342 100644 --- a/src/test/java/org/restheart/test/integration/DocIdTypeIT.java +++ b/src/test/java/org/restheart/test/integration/DocIdTypeIT.java @@ -117,7 +117,7 @@ public void testPostCollectionString() throws Exception { .build(); // *** POST tmpcoll - resp = adminExecutor.execute(Request.Post(collectionTmpUriInt).bodyString("{_id:'54c965cbc2e64568e235b711', a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Post(collectionTmpUriInt).bodyString("{_id:{'$oid':'54c965cbc2e64568e235b711'}, a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); HttpResponse httpResp = check("check post coll1 again", resp, HttpStatus.SC_CREATED); Header[] headers = httpResp.getHeaders(Headers.LOCATION_STRING); @@ -135,12 +135,12 @@ public void testPostCollectionString() throws Exception { resp = adminExecutor.execute(Request.Get(createdDocUri).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - assertTrue("check created doc content", content.get("_id").asString().equals("54c965cbc2e64568e235b711")); + assertTrue("check created doc content", content.get("_id").asObject().get("$oid").asString().equals("54c965cbc2e64568e235b711")); assertNotNull("check created doc content", content.get("_etag")); assertNotNull("check created doc content", content.get("a")); assertTrue("check created doc content", content.get("a").asInt() == 1); - // *** filter - case 1 - without detect_oids=false should not find it + // *** filter - case 1 - with string id should not find it URI collectionTmpUriSearch = new URIBuilder() .setScheme(HTTP) .setHost(HOST) @@ -154,14 +154,13 @@ public void testPostCollectionString() throws Exception { content = JsonObject.readFrom(resp.returnContent().asString()); assertTrue("check created doc content", content.get("_returned").asInt() == 0); - // *** filter - case 1 - with detect_oids=false should find it + // *** filter - case 1 - with oid id should find it collectionTmpUriSearch = new URIBuilder() .setScheme(HTTP) .setHost(HOST) .setPort(conf.getHttpPort()) .setPath("/" + dbTmpName + "/" + collectionTmpName) - .setParameter("filter", "{'_id':'54c965cbc2e64568e235b711'}") - .setParameter("detect_oids", "false") + .setParameter("filter", "{'_id':{'$oid':'54c965cbc2e64568e235b711'}}") .build(); resp = adminExecutor.execute(Request.Get(collectionTmpUriSearch).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); diff --git a/src/test/java/org/restheart/test/integration/PatchCollectionIT.java b/src/test/java/org/restheart/test/integration/PatchCollectionIT.java index 1ff60dd3d4..76d92f756e 100644 --- a/src/test/java/org/restheart/test/integration/PatchCollectionIT.java +++ b/src/test/java/org/restheart/test/integration/PatchCollectionIT.java @@ -65,7 +65,7 @@ public void testPatchCollection() throws Exception { JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - String etag = content.get("_etag").asString(); + String etag = content.get("_etag").asObject().get("$oid").asString(); // try to patch with correct etag resp = adminExecutor.execute(Request.Patch(collectionTmpUri).bodyString("{b:2}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, etag)); @@ -77,7 +77,7 @@ public void testPatchCollection() throws Exception { assertNotNull("check patched content", content.get("a")); assertNotNull("check patched content", content.get("b")); assertTrue("check patched content", content.get("a").asInt() == 1 && content.get("b").asInt() == 2); - etag = content.get("_etag").asString(); + etag = content.get("_etag").asObject().get("$oid").asString(); // try to patch reserved field name resp = adminExecutor.execute(Request.Patch(collectionTmpUri).bodyString("{_embedded:\"a\"}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, etag)); diff --git a/src/test/java/org/restheart/test/integration/PatchDBIT.java b/src/test/java/org/restheart/test/integration/PatchDBIT.java index 7afa4ea7b3..63b319e8da 100644 --- a/src/test/java/org/restheart/test/integration/PatchDBIT.java +++ b/src/test/java/org/restheart/test/integration/PatchDBIT.java @@ -57,7 +57,7 @@ public void testPatchDB() throws Exception { JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - String etag = content.get("_etag").asString(); + String etag = content.get("_etag").asObject().get("$oid").asString(); // try to patch with correct etag resp = adminExecutor.execute(Request.Patch(dbTmpUri).bodyString("{b:2}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, etag)); @@ -69,7 +69,7 @@ public void testPatchDB() throws Exception { assertNotNull("check patched content", content.get("a")); assertNotNull("check patched content", content.get("b")); assertTrue("check patched content", content.get("a").asInt() == 1 && content.get("b").asInt() == 2); - etag = content.get("_etag").asString(); + etag = content.get("_etag").asObject().get("$oid").asString(); // try to patch reserved field name resp = adminExecutor.execute(Request.Patch(dbTmpUri).bodyString("{_embedded:\"a\"}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, etag)); diff --git a/src/test/java/org/restheart/test/integration/PatchDocumentIT.java b/src/test/java/org/restheart/test/integration/PatchDocumentIT.java index c6957b7b5d..6c38d3087f 100644 --- a/src/test/java/org/restheart/test/integration/PatchDocumentIT.java +++ b/src/test/java/org/restheart/test/integration/PatchDocumentIT.java @@ -69,7 +69,7 @@ public void testPatchDocument() throws Exception { JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - String etag = content.get("_etag").asString(); + String etag = content.get("_etag").asObject().get("$oid").asString(); // try to patch with correct etag resp = adminExecutor.execute(Request.Patch(documentTmpUri).bodyString("{b:2}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, etag)); diff --git a/src/test/java/org/restheart/test/integration/PostCollectionIT.java b/src/test/java/org/restheart/test/integration/PostCollectionIT.java index 7bf6f8ebea..f4cbba304a 100644 --- a/src/test/java/org/restheart/test/integration/PostCollectionIT.java +++ b/src/test/java/org/restheart/test/integration/PostCollectionIT.java @@ -77,19 +77,19 @@ public void testPostCollection() throws Exception { assertNotNull("check created doc content", content.get("a")); assertTrue("check created doc content", content.get("a").asInt() == 1); - String _id = content.get("_id").asString(); - String _etag = content.get("_etag").asString(); + String _id = content.get("_id").asObject().get("$oid").asString(); + String _etag = content.get("_etag").asObject().get("$oid").asString(); // try to post with _id without etag - resp = adminExecutor.execute(Request.Post(collectionTmpUri).bodyString("{_id:\"" + _id + "\", a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Post(collectionTmpUri).bodyString("{_id:{\"$oid\":\"" + _id + "\"}, a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); check("check post created doc without etag", resp, HttpStatus.SC_CONFLICT); // try to post with wrong etag - resp = adminExecutor.execute(Request.Post(collectionTmpUri).bodyString("{_id:\"" + _id + "\", a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, "pippoetag")); + resp = adminExecutor.execute(Request.Post(collectionTmpUri).bodyString("{_id:{\"$oid\":\"" + _id + "\"}, a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, "pippoetag")); check("check put created doc with wrong etag", resp, HttpStatus.SC_PRECONDITION_FAILED); // try to post with correct etag - resp = adminExecutor.execute(Request.Post(collectionTmpUri).bodyString("{_id:\"" + _id + "\", a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, _etag)); + resp = adminExecutor.execute(Request.Post(collectionTmpUri).bodyString("{_id:{\"$oid\":\"" + _id + "\"}, a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, _etag)); check("check post created doc with correct etag", resp, HttpStatus.SC_OK); } finally { mongoClient.dropDatabase(dbTmpName); diff --git a/src/test/java/org/restheart/test/integration/PutCollectionIT.java b/src/test/java/org/restheart/test/integration/PutCollectionIT.java index c1fe9f1a83..e027f23ad7 100644 --- a/src/test/java/org/restheart/test/integration/PutCollectionIT.java +++ b/src/test/java/org/restheart/test/integration/PutCollectionIT.java @@ -61,7 +61,7 @@ public void testPutCollection() throws Exception { JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - String etag = content.get("_etag").asString(); + String etag = content.get("_etag").asObject().get("$oid").asString(); // try to put with correct etag resp = adminExecutor.execute(Request.Put(collectionTmpUri).bodyString("{b:2}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, etag)); diff --git a/src/test/java/org/restheart/test/integration/PutDBIT.java b/src/test/java/org/restheart/test/integration/PutDBIT.java index 8a69d492c2..f3a4130bc2 100644 --- a/src/test/java/org/restheart/test/integration/PutDBIT.java +++ b/src/test/java/org/restheart/test/integration/PutDBIT.java @@ -57,7 +57,7 @@ public void testPutCollection() throws Exception { JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - String etag = content.get("_etag").asString(); + String etag = content.get("_etag").asObject().get("$oid").asString(); // try to put with correct etag resp = adminExecutor.execute(Request.Put(dbTmpUri).bodyString("{b:2}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, etag)); diff --git a/src/test/java/org/restheart/test/integration/PutDocumentIT.java b/src/test/java/org/restheart/test/integration/PutDocumentIT.java index 4a60ea234b..80f7be65c9 100644 --- a/src/test/java/org/restheart/test/integration/PutDocumentIT.java +++ b/src/test/java/org/restheart/test/integration/PutDocumentIT.java @@ -65,7 +65,7 @@ public void testPutDocument() throws Exception { JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - String etag = content.get("_etag").asString(); + String etag = content.get("_etag").asObject().get("$oid").asString(); // try to put with correct etag resp = adminExecutor.execute(Request.Put(documentTmpUri).bodyString("{b:2}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE).addHeader(Headers.IF_MATCH_STRING, etag)); From aa6663de68564f482904d042e55f0694faf8d701 Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Wed, 11 Feb 2015 15:20:03 +0100 Subject: [PATCH 09/17] managed (unlikely) case where stored _etag is not an ObjectId code refactoring fixed GET file --- .../java/org/restheart/db/CollectionDAO.java | 50 +------ .../java/org/restheart/db/DBCursorPool.java | 2 +- src/main/java/org/restheart/db/DbsDAO.java | 6 +- .../java/org/restheart/db/DocumentDAO.java | 29 +++-- .../org/restheart/hal/Representation.java | 14 +- .../restheart/handlers/RequestContext.java | 6 +- .../handlers/RequestDispacherHandler.java | 2 + .../CollectionRepresentationFactory.java | 12 +- .../collection/GetCollectionHandler.java | 2 +- .../handlers/document/GetDocumentHandler.java | 9 +- .../handlers/files/GetFileBinaryHandler.java | 122 ++++++++++++++++++ .../handlers/files/GetFileHandler.java | 97 +------------- .../org/restheart/utils/RequestHelper.java | 14 +- 13 files changed, 188 insertions(+), 177 deletions(-) create mode 100644 src/main/java/org/restheart/handlers/files/GetFileBinaryHandler.java diff --git a/src/main/java/org/restheart/db/CollectionDAO.java b/src/main/java/org/restheart/db/CollectionDAO.java index bbc45fc13c..72e42c9e2a 100644 --- a/src/main/java/org/restheart/db/CollectionDAO.java +++ b/src/main/java/org/restheart/db/CollectionDAO.java @@ -27,7 +27,6 @@ import com.mongodb.util.JSONParseException; import org.restheart.utils.HttpStatus; import java.time.Instant; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import org.bson.BSONObject; @@ -219,13 +218,12 @@ ArrayList getCollectionData( pagesize--; } + // add the _lastupdated_on in case the _etag field is present and its value is an ObjectId ret.forEach(row -> { Object etag = row.get("_etag"); - if (etag != null && ObjectId.isValid("" + etag)) { - ObjectId _etag = new ObjectId("" + etag); - - row.put("_lastupdated_on", Instant.ofEpochSecond(_etag.getTimestamp()).toString()); + if (etag != null && etag instanceof ObjectId) { + row.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId)etag).getTimestamp()).toString()); } } ); @@ -250,10 +248,8 @@ public DBObject getCollectionProps(String dbName, String collName, boolean fixMi Object etag = properties.get("_etag"); - if (etag != null && ObjectId.isValid("" + etag)) { - ObjectId oid = new ObjectId("" + etag); - - properties.put("_lastupdated_on", Instant.ofEpochSecond(oid.getTimestamp()).toString()); + if (etag != null && etag instanceof ObjectId) { + properties.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId)etag).getTimestamp()).toString()); } } else if (fixMissingProperties) { new PropsFixer().addCollectionProps(dbName, collName); @@ -385,38 +381,4 @@ private void initDefaultIndexes(DBCollection coll) { coll.createIndex(new BasicDBObject("_etag", 1), new BasicDBObject("name", "_etag_idx")); coll.createIndex(new BasicDBObject("_created_on", 1), new BasicDBObject("name", "_created_on_idx")); } - - private Deque replaceObjectIdsInFilters(Deque filters) { - if (filters == null) { - return null; - } - - ArrayDeque ret = new ArrayDeque<>(); - - filters.stream().forEach((filter) -> { - BSONObject _filter = (BSONObject) JSON.parse(filter); - - replaceObjectIdsInFilters(_filter); - - ret.add(_filter.toString()); - }); - - return ret; - } - - private void replaceObjectIdsInFilters(BSONObject source) { - if (source == null) { - return; - } - - source.keySet().stream().forEach((key) -> { - Object value = source.get(key); - - if (value instanceof BSONObject) { - replaceObjectIdsInFilters((BSONObject) value); - } else if (ObjectId.isValid(value.toString())) { - source.put(key, new ObjectId(value.toString())); - } - }); - } -} +} \ No newline at end of file diff --git a/src/main/java/org/restheart/db/DBCursorPool.java b/src/main/java/org/restheart/db/DBCursorPool.java index 5c5b0c197c..1daf6ad3b1 100644 --- a/src/main/java/org/restheart/db/DBCursorPool.java +++ b/src/main/java/org/restheart/db/DBCursorPool.java @@ -101,7 +101,7 @@ private DBCursorPool(DbsDAO dbsDAO) { public synchronized SkippedDBCursor get(DBCursorPoolEntryKey key, EAGER_CURSOR_ALLOCATION_POLICY allocationPolicy) { if (key.getSkipped() < SKIP_SLICE_LINEAR_WIDTH) { - LOGGER.debug("no cursor to reuse found with skipped {} that is less than SKIP_SLICE_WIDTH {}", key.getSkipped(), SKIP_SLICE_LINEAR_WIDTH); + LOGGER.trace("no cursor to reuse found with skipped {} that is less than SKIP_SLICE_WIDTH {}", key.getSkipped(), SKIP_SLICE_LINEAR_WIDTH); return null; } diff --git a/src/main/java/org/restheart/db/DbsDAO.java b/src/main/java/org/restheart/db/DbsDAO.java index e1e0bd843c..427b487353 100644 --- a/src/main/java/org/restheart/db/DbsDAO.java +++ b/src/main/java/org/restheart/db/DbsDAO.java @@ -160,10 +160,8 @@ public DBObject getDatabaseProperties(String dbName, boolean fixMissingPropertie Object etag = row.get("_etag"); - if (etag != null && ObjectId.isValid("" + etag)) { - ObjectId oid = new ObjectId("" + etag); - - row.put("_lastupdated_on", Instant.ofEpochSecond(oid.getTimestamp()).toString()); + if (etag != null && etag instanceof ObjectId) { + row.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId)etag).getTimestamp()).toString()); } } else if (fixMissingProperties) { new PropsFixer().addDbProps(dbName); diff --git a/src/main/java/org/restheart/db/DocumentDAO.java b/src/main/java/org/restheart/db/DocumentDAO.java index 193df6206b..8e376ddd3e 100644 --- a/src/main/java/org/restheart/db/DocumentDAO.java +++ b/src/main/java/org/restheart/db/DocumentDAO.java @@ -23,7 +23,6 @@ import com.mongodb.DBObject; import com.mongodb.MongoClient; import org.restheart.utils.HttpStatus; -import org.restheart.utils.RequestHelper; import java.time.Instant; import org.bson.types.ObjectId; import org.slf4j.Logger; @@ -204,24 +203,28 @@ public int deleteDocument(String dbName, String collName, Object documentId, Obj } private int optimisticCheckEtag(DBCollection coll, DBObject oldDocument, ObjectId requestEtag, int httpStatusIfOk) { - Object oldEtag = RequestHelper.getEtagAsObjectId(oldDocument.get("_etag")); + Object oldEtag = oldDocument.get("_etag"); - if (requestEtag == null && oldEtag != null) { + if (oldEtag == null) { // well we don't had an etag there so fine + return httpStatusIfOk; + } + + if (!(oldEtag instanceof ObjectId)) { // well the _etag is not an ObjectId. no check is possible + return httpStatusIfOk; + } + + if (requestEtag == null) { coll.save(oldDocument); return HttpStatus.SC_CONFLICT; } - if (oldEtag == null) { // well we don't had an etag there so fine - return HttpStatus.SC_NO_CONTENT; + if (oldEtag.equals(requestEtag)) { + return httpStatusIfOk; // ok they match } else { - if (oldEtag.equals(requestEtag)) { - return httpStatusIfOk; // ok they match - } else { - // oopps, we need to restore old document - // they call it optimistic lock strategy - coll.save(oldDocument); - return HttpStatus.SC_PRECONDITION_FAILED; - } + // oopps, we need to restore old document + // they call it optimistic lock strategy + coll.save(oldDocument); + return HttpStatus.SC_PRECONDITION_FAILED; } } } \ No newline at end of file diff --git a/src/main/java/org/restheart/hal/Representation.java b/src/main/java/org/restheart/hal/Representation.java index 25a0c24782..cdd8c0fde3 100644 --- a/src/main/java/org/restheart/hal/Representation.java +++ b/src/main/java/org/restheart/hal/Representation.java @@ -24,7 +24,7 @@ import com.mongodb.util.ObjectSerializer; import java.util.Objects; import org.bson.BSONObject; -import org.bson.types.ObjectId; +import org.restheart.handlers.RequestContext; /** * @@ -56,6 +56,18 @@ public Representation(String href) { links.put("self", new BasicDBObject("href", href)); } + + public RequestContext.TYPE getType() { + if (properties == null) + return null; + + Object _type = properties.get("_type"); + + if (_type == null) + return null; + + return RequestContext.TYPE.valueOf(_type.toString()); + } BasicDBObject getDBObject() { BasicDBObject ret = new BasicDBObject(properties); diff --git a/src/main/java/org/restheart/handlers/RequestContext.java b/src/main/java/org/restheart/handlers/RequestContext.java index e4bc40af69..7a4134e355 100644 --- a/src/main/java/org/restheart/handlers/RequestContext.java +++ b/src/main/java/org/restheart/handlers/RequestContext.java @@ -46,7 +46,8 @@ public enum TYPE { COLLECTION_INDEXES, INDEX, FILES_BUCKET, - FILE + FILE, + FILE_BINARY }; public enum METHOD { @@ -104,7 +105,6 @@ public enum DOC_ID_TYPE { private Deque filter = null; private Deque sortBy = null; private DOC_ID_TYPE docIdType = DOC_ID_TYPE.STRING_OBJECTID; - private boolean detectObjectIds = false; private Object documentId; private String unmappedRequestUri = null; @@ -187,7 +187,7 @@ protected static TYPE selectRequestType(String[] pathTokens) { type = TYPE.FILE; } else if (pathTokens.length == 5 && pathTokens[4].equalsIgnoreCase(BINARY_CONTENT)) { // URL: /db/bucket.file/xxx/binary - type = TYPE.FILE; + type = TYPE.FILE_BINARY; } else { type = TYPE.DOCUMENT; } diff --git a/src/main/java/org/restheart/handlers/RequestDispacherHandler.java b/src/main/java/org/restheart/handlers/RequestDispacherHandler.java index f5cbddb52c..7d78ae92c8 100644 --- a/src/main/java/org/restheart/handlers/RequestDispacherHandler.java +++ b/src/main/java/org/restheart/handlers/RequestDispacherHandler.java @@ -42,6 +42,7 @@ import static org.restheart.handlers.RequestContext.TYPE; import org.restheart.handlers.files.DeleteBucketHandler; import org.restheart.handlers.files.DeleteFileHandler; +import org.restheart.handlers.files.GetFileBinaryHandler; import org.restheart.handlers.files.GetFileHandler; import org.restheart.handlers.files.PostFileHandler; import org.restheart.handlers.files.PutBucketHandler; @@ -123,6 +124,7 @@ private void defaultInit() { putPipedHttpHandler(TYPE.FILES_BUCKET, METHOD.PUT, new PutBucketHandler()); putPipedHttpHandler(TYPE.FILES_BUCKET, METHOD.DELETE, new DeleteBucketHandler()); putPipedHttpHandler(TYPE.FILE, METHOD.GET, new GetFileHandler()); + putPipedHttpHandler(TYPE.FILE_BINARY, METHOD.GET, new GetFileBinaryHandler()); putPipedHttpHandler(TYPE.FILE, METHOD.PUT, new PutFileHandler()); putPipedHttpHandler(TYPE.FILE, METHOD.DELETE, new DeleteFileHandler()); } diff --git a/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java b/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java index d88953f5f2..8bc4418af8 100644 --- a/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java +++ b/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java @@ -27,7 +27,6 @@ import org.restheart.utils.URLUtils; import io.undertow.server.HttpServerExchange; import java.util.List; -import org.bson.types.ObjectId; import org.restheart.handlers.AbstractRepresentationFactory; /** @@ -68,7 +67,7 @@ protected Representation getRepresentation(HttpServerExchange exchange, RequestC addPaginationLinks(exchange, context, size, rep); addLinkTemplatesAndCuries(exchange, context, rep, requestPath); - + return rep; } @@ -106,9 +105,14 @@ private void embeddedDocuments(List embeddedData, String requestPath, Representation nrep = DocumentRepresentationFactory.getDocument(requestPath + "/" + _id.toString(), exchange, context, d); - nrep.addProperty("_type", RequestContext.TYPE.DOCUMENT.name()); + if (rep.getType() == RequestContext.TYPE.FILES_BUCKET) { + nrep.addProperty("_type", RequestContext.TYPE.FILE.name()); + rep.addRepresentation("rh:file", nrep); + } else { + nrep.addProperty("_type", RequestContext.TYPE.DOCUMENT.name()); + rep.addRepresentation("rh:doc", nrep); + } - rep.addRepresentation("rh:doc", nrep); } } } diff --git a/src/main/java/org/restheart/handlers/collection/GetCollectionHandler.java b/src/main/java/org/restheart/handlers/collection/GetCollectionHandler.java index 0418a5c49f..a561560ef7 100644 --- a/src/main/java/org/restheart/handlers/collection/GetCollectionHandler.java +++ b/src/main/java/org/restheart/handlers/collection/GetCollectionHandler.java @@ -95,7 +95,7 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t // ***** return NOT_FOUND from here if collection is not existing // (this is to avoid to check existance via the slow CollectionDAO.checkCollectionExists) - if ((context.getPagesize() > 0 && data.isEmpty()) && (context.getCollectionProps() == null || context.getCollectionProps().keySet().isEmpty())) { + if ((context.getPagesize() > 0 && (data == null || data.isEmpty())) && (context.getCollectionProps() == null || context.getCollectionProps().keySet().isEmpty())) { ResponseHelper.endExchange(exchange, HttpStatus.SC_NOT_FOUND); return; } diff --git a/src/main/java/org/restheart/handlers/document/GetDocumentHandler.java b/src/main/java/org/restheart/handlers/document/GetDocumentHandler.java index c184c8be52..3ccdfd841c 100644 --- a/src/main/java/org/restheart/handlers/document/GetDocumentHandler.java +++ b/src/main/java/org/restheart/handlers/document/GetDocumentHandler.java @@ -61,14 +61,13 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t Object etag = document.get("_etag"); - if (etag != null && ObjectId.isValid("" + etag)) { - ObjectId _etag = new ObjectId("" + etag); - - document.put("_lastupdated_on", Instant.ofEpochSecond(_etag.getTimestamp()).toString()); + if (etag != null && etag instanceof ObjectId) { + // add the _lastupdated_on in case the _etag field is present and its value is an ObjectId + document.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId)etag).getTimestamp()).toString()); // in case the request contains the IF_NONE_MATCH header with the current etag value, // just return 304 NOT_MODIFIED code - if (RequestHelper.checkReadEtag(exchange, etag.toString())) { + if (RequestHelper.checkReadEtag(exchange, (ObjectId)etag)) { ResponseHelper.endExchange(exchange, HttpStatus.SC_NOT_MODIFIED); return; } diff --git a/src/main/java/org/restheart/handlers/files/GetFileBinaryHandler.java b/src/main/java/org/restheart/handlers/files/GetFileBinaryHandler.java new file mode 100644 index 0000000000..84487cab0c --- /dev/null +++ b/src/main/java/org/restheart/handlers/files/GetFileBinaryHandler.java @@ -0,0 +1,122 @@ +/* + * RESTHeart - the data REST API server + * 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 . + */ +package org.restheart.handlers.files; + +import com.mongodb.BasicDBObject; +import com.mongodb.gridfs.GridFS; +import com.mongodb.gridfs.GridFSDBFile; +import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; +import java.io.IOException; +import java.time.Instant; +import org.bson.types.ObjectId; +import org.restheart.handlers.PipedHttpHandler; +import org.restheart.handlers.RequestContext; +import org.restheart.utils.HttpStatus; +import org.restheart.utils.RequestHelper; +import org.restheart.utils.ResponseHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author Maurizio Turatti + */ +public class GetFileBinaryHandler extends PipedHttpHandler { + + public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + public static final String CONTENT_TRANSFER_ENCODING_BINARY = "binary"; + + private static final Logger LOGGER = LoggerFactory.getLogger(GetFileBinaryHandler.class); + + public GetFileBinaryHandler() { + super(); + } + + @Override + public void handleRequest(HttpServerExchange exchange, RequestContext context) throws Exception { + LOGGER.debug("GET " + exchange.getRequestURL()); + final String bucket = extractBucketName(context.getCollectionName()); + + GridFS gridfs = new GridFS(getDatabase().getDB(context.getDBName()), bucket); + GridFSDBFile dbsfile = gridfs.findOne(new BasicDBObject("_id", context.getDocumentId())); + + if (dbsfile == null) { + fileNotFound(context, exchange); + } else { + if (!checkEtag(exchange, dbsfile)) { + sendBinaryContent(dbsfile, exchange); + } + } + } + + private boolean checkEtag(HttpServerExchange exchange, GridFSDBFile dbsfile) { + if (dbsfile != null) { + Object etag = dbsfile.get("_etag"); + + if (etag != null && etag instanceof ObjectId) { + // add the _lastupdated_on in case the _etag field is present and its value is an ObjectId + dbsfile.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId)etag).getTimestamp()).toString()); + + // in case the request contains the IF_NONE_MATCH header with the current etag value, + // just return 304 NOT_MODIFIED code + if (RequestHelper.checkReadEtag(exchange, (ObjectId)etag)) { + ResponseHelper.endExchange(exchange, HttpStatus.SC_NOT_MODIFIED); + return true; + } + } + } + + return false; + } + + private void fileNotFound(RequestContext context, HttpServerExchange exchange) { + final String errMsg = String.format("File with ID <%s> not found", context.getDocumentId()); + LOGGER.error(errMsg); + ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_NOT_FOUND, errMsg); + } + + private void sendBinaryContent(final GridFSDBFile dbsfile, final HttpServerExchange exchange) throws IOException { + LOGGER.debug("Filename = {}", dbsfile.getFilename()); + LOGGER.debug("Content length = {}", dbsfile.getLength()); + + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, APPLICATION_OCTET_STREAM); + exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, dbsfile.getLength()); + exchange.getResponseHeaders().put(Headers.CONTENT_DISPOSITION, + String.format("inline; filename=\"%s\"", extractFilename(dbsfile))); + exchange.getResponseHeaders().put(Headers.CONTENT_TRANSFER_ENCODING, CONTENT_TRANSFER_ENCODING_BINARY); + ResponseHelper.injectEtagHeader(exchange, dbsfile); + + exchange.setResponseCode(HttpStatus.SC_OK); + + dbsfile.writeTo(exchange.getOutputStream()); + exchange.endExchange(); + } + + private String extractFilename(final GridFSDBFile dbsfile) { + return dbsfile.getFilename() != null ? dbsfile.getFilename() : dbsfile.getId().toString(); + } + + static String extractBucketName(final String collectionName) { + return collectionName.split("\\.")[0]; + } + + GetFileBinaryHandler(Object object, Object object0) { + super(null, null); + } +} diff --git a/src/main/java/org/restheart/handlers/files/GetFileHandler.java b/src/main/java/org/restheart/handlers/files/GetFileHandler.java index 22e406185e..96de718c0f 100644 --- a/src/main/java/org/restheart/handlers/files/GetFileHandler.java +++ b/src/main/java/org/restheart/handlers/files/GetFileHandler.java @@ -17,103 +17,12 @@ */ package org.restheart.handlers.files; -import com.mongodb.BasicDBObject; -import com.mongodb.gridfs.GridFS; -import com.mongodb.gridfs.GridFSDBFile; -import io.undertow.server.HttpServerExchange; -import io.undertow.util.Headers; -import java.io.IOException; -import org.bson.types.ObjectId; -import org.restheart.handlers.PipedHttpHandler; -import org.restheart.handlers.RequestContext; -import org.restheart.utils.HttpStatus; -import org.restheart.utils.RequestHelper; -import org.restheart.utils.ResponseHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.restheart.handlers.document.GetDocumentHandler; /** * - * @author Maurizio Turatti + * @author Andrea Di Cesare */ -public class GetFileHandler extends PipedHttpHandler { - - public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; - public static final String CONTENT_TRANSFER_ENCODING_BINARY = "binary"; +public class GetFileHandler extends GetDocumentHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(GetFileHandler.class); - - public GetFileHandler() { - super(); - } - - @Override - public void handleRequest(HttpServerExchange exchange, RequestContext context) throws Exception { - LOGGER.debug("GET " + exchange.getRequestURL()); - final String bucket = extractBucketName(context.getCollectionName()); - - GridFS gridfs = new GridFS(getDatabase().getDB(context.getDBName()), bucket); - GridFSDBFile dbsfile = gridfs.findOne(new BasicDBObject("_id", context.getDocumentId())); - - if (dbsfile == null) { - fileNotFound(context, exchange); - } else { - if (!checkEtag(exchange, dbsfile)) { - sendBinaryContent(dbsfile, exchange); - } - } - } - - private boolean checkEtag(HttpServerExchange exchange, GridFSDBFile dbsfile) { - if (dbsfile != null) { - Object etag = dbsfile.get("_etag"); - - if (etag != null && ObjectId.isValid("" + etag)) { - - // in case the request contains the IF_NONE_MATCH header with the current etag value, - // just return 304 NOT_MODIFIED code - if (RequestHelper.checkReadEtag(exchange, etag.toString())) { - ResponseHelper.endExchange(exchange, HttpStatus.SC_NOT_MODIFIED); - return true; - } - } - } - - return false; - } - - private void fileNotFound(RequestContext context, HttpServerExchange exchange) { - final String errMsg = String.format("File with ID <%s> not found", context.getDocumentId()); - LOGGER.error(errMsg); - ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_NOT_FOUND, errMsg); - } - - private void sendBinaryContent(final GridFSDBFile dbsfile, final HttpServerExchange exchange) throws IOException { - LOGGER.debug("Filename = {}", dbsfile.getFilename()); - LOGGER.debug("Content length = {}", dbsfile.getLength()); - - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, APPLICATION_OCTET_STREAM); - exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, dbsfile.getLength()); - exchange.getResponseHeaders().put(Headers.CONTENT_DISPOSITION, - String.format("inline; filename=\"%s\"", extractFilename(dbsfile))); - exchange.getResponseHeaders().put(Headers.CONTENT_TRANSFER_ENCODING, CONTENT_TRANSFER_ENCODING_BINARY); - ResponseHelper.injectEtagHeader(exchange, dbsfile); - - exchange.setResponseCode(HttpStatus.SC_OK); - - dbsfile.writeTo(exchange.getOutputStream()); - exchange.endExchange(); - } - - private String extractFilename(final GridFSDBFile dbsfile) { - return dbsfile.getFilename() != null ? dbsfile.getFilename() : dbsfile.getId().toString(); - } - - static String extractBucketName(final String collectionName) { - return collectionName.split("\\.")[0]; - } - - GetFileHandler(Object object, Object object0) { - super(null, null); - } } diff --git a/src/main/java/org/restheart/utils/RequestHelper.java b/src/main/java/org/restheart/utils/RequestHelper.java index a9d5083126..d3692b5147 100644 --- a/src/main/java/org/restheart/utils/RequestHelper.java +++ b/src/main/java/org/restheart/utils/RequestHelper.java @@ -34,20 +34,20 @@ public class RequestHelper { * @param etag * @return */ - public static boolean checkReadEtag(HttpServerExchange exchange, String etag) { + public static boolean checkReadEtag(HttpServerExchange exchange, ObjectId etag) { if (etag == null) { return false; } HeaderValues vs = exchange.getRequestHeaders().get(Headers.IF_NONE_MATCH); - return vs == null || vs.getFirst() == null ? false : vs.getFirst().equals(etag); + return vs == null || vs.getFirst() == null ? false : vs.getFirst().equals(etag.toString()); } /** * * @param exchange - * @return + * @return the etag ObjectId value or null in case the IF_MATCH header is not present. If the header contains an invalid ObjectId string value returns a new ObjectId (the check will fail for sure) */ public static ObjectId getWriteEtag(HttpServerExchange exchange) { HeaderValues vs = exchange.getRequestHeaders().get(Headers.IF_MATCH); @@ -60,15 +60,15 @@ public static ObjectId getWriteEtag(HttpServerExchange exchange) { * @param etag * @return */ - public static ObjectId getEtagAsObjectId(Object etag) { + private static ObjectId getEtagAsObjectId(String etag) { if (etag == null) { return null; } - if (ObjectId.isValid("" + etag)) { - return new ObjectId("" + etag); + if (ObjectId.isValid(etag)) { + return new ObjectId(etag); } else { return new ObjectId(); } } -} \ No newline at end of file +} From e3f7a66f518c224a931981db16dc06f5926875a1 Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Wed, 11 Feb 2015 15:26:32 +0100 Subject: [PATCH 10/17] added test case for request type FILE_BINARY --- src/test/java/org/restheart/handlers/RequestContextTest.java | 2 +- .../java/org/restheart/handlers/files/GetFileHandlerTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/restheart/handlers/RequestContextTest.java b/src/test/java/org/restheart/handlers/RequestContextTest.java index 7f1b458ee6..ea58f08df1 100644 --- a/src/test/java/org/restheart/handlers/RequestContextTest.java +++ b/src/test/java/org/restheart/handlers/RequestContextTest.java @@ -106,7 +106,7 @@ public void test_FILE_selectRequestType() { assertEquals(RequestContext.TYPE.FILE, RequestContext.selectRequestType(pathTokens)); pathTokens = "/db/mybucket.files/123/binary".split("/"); - assertEquals(RequestContext.TYPE.FILE, RequestContext.selectRequestType(pathTokens)); + assertEquals(RequestContext.TYPE.FILE_BINARY, RequestContext.selectRequestType(pathTokens)); pathTokens = "/db/mybucket.files/123/456".split("/"); assertEquals(RequestContext.TYPE.FILE, RequestContext.selectRequestType(pathTokens)); diff --git a/src/test/java/org/restheart/handlers/files/GetFileHandlerTest.java b/src/test/java/org/restheart/handlers/files/GetFileHandlerTest.java index e99b52aa84..df3ca83eaf 100644 --- a/src/test/java/org/restheart/handlers/files/GetFileHandlerTest.java +++ b/src/test/java/org/restheart/handlers/files/GetFileHandlerTest.java @@ -32,7 +32,7 @@ public GetFileHandlerTest() { @Test public void testExtractBucket() { System.out.println("testExtractBucket"); - assertEquals("mybucket", GetFileHandler.extractBucketName("mybucket.files")); + assertEquals("mybucket", GetFileBinaryHandler.extractBucketName("mybucket.files")); } } From 89c22703d826d47b1c1fd95034283f51fca6bbf6 Mon Sep 17 00:00:00 2001 From: Maurizio Turatti Date: Wed, 11 Feb 2015 18:30:07 +0100 Subject: [PATCH 11/17] Tests refactoring and bugfix --- .../java/org/restheart/Configuration.java | 4 +- src/main/java/org/restheart/Shutdowner.java | 2 + src/main/java/org/restheart/db/GridFsDAO.java | 1 + .../handlers/files/PutFileHandler.java | 3 - .../handlers/files/GetFileHandlerIT.java | 56 +-- .../restheart/test/integration/AbstactIT.java | 365 +++++------------- .../test/integration/DocIdTypeIT.java | 90 ++--- 7 files changed, 165 insertions(+), 356 deletions(-) diff --git a/src/main/java/org/restheart/Configuration.java b/src/main/java/org/restheart/Configuration.java index 57a992c8ac..9151dcc3f1 100644 --- a/src/main/java/org/restheart/Configuration.java +++ b/src/main/java/org/restheart/Configuration.java @@ -602,8 +602,8 @@ public Configuration(final Path confFilePath, boolean silent) throws Configurati List> mongoServersDefault = new ArrayList<>(); Map defaultMongoServer = new HashMap<>(); - defaultMongoServer.put(MONGO_HOST_KEY, "127.0.0.1"); - defaultMongoServer.put(MONGO_PORT_KEY, 27017); + defaultMongoServer.put(MONGO_HOST_KEY, DEFAULT_MONGO_HOST); + defaultMongoServer.put(MONGO_PORT_KEY, DEFAULT_MONGO_PORT); mongoServersDefault.add(defaultMongoServer); mongoServers = getAsListOfMaps(conf, MONGO_SERVERS_KEY, mongoServersDefault); diff --git a/src/main/java/org/restheart/Shutdowner.java b/src/main/java/org/restheart/Shutdowner.java index 3c8a054ad0..1963387890 100644 --- a/src/main/java/org/restheart/Shutdowner.java +++ b/src/main/java/org/restheart/Shutdowner.java @@ -31,6 +31,8 @@ public class Shutdowner { private static final Logger LOGGER = LoggerFactory.getLogger(Shutdowner.class); public static void main(final String[] args) { + LOGGER.info("Shutdowner called..."); + if (askingForHelp(args)) { LOGGER.info("usage: java -cp restheart.jar org.restheart.Shutdowner [configuration file]."); LOGGER.info("shutdown --help\t\tprints this help message and exits."); diff --git a/src/main/java/org/restheart/db/GridFsDAO.java b/src/main/java/org/restheart/db/GridFsDAO.java index 7bdc46c88a..d18c11e980 100644 --- a/src/main/java/org/restheart/db/GridFsDAO.java +++ b/src/main/java/org/restheart/db/GridFsDAO.java @@ -108,6 +108,7 @@ public int deleteFile(Database db, String dbName, String bucketName, Object file } } + @Override public void deleteChunksCollection(Database db, String dbName, String bucketName) { String chunksCollName = extractBucketName(bucketName).concat(".chunks"); client.getDB(dbName).getCollection(chunksCollName).drop(); diff --git a/src/main/java/org/restheart/handlers/files/PutFileHandler.java b/src/main/java/org/restheart/handlers/files/PutFileHandler.java index 53ea6df9bf..d9b325061b 100644 --- a/src/main/java/org/restheart/handlers/files/PutFileHandler.java +++ b/src/main/java/org/restheart/handlers/files/PutFileHandler.java @@ -26,8 +26,6 @@ import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.server.handlers.form.FormData; import io.undertow.server.handlers.form.FormDataParser; -import io.undertow.util.HttpString; -import org.bson.types.ObjectId; import org.restheart.db.Database; import org.restheart.db.GridFsDAO; import org.restheart.db.GridFsRepository; @@ -39,7 +37,6 @@ import org.restheart.utils.UnsupportedDocumentIdException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.restheart.utils.URLUtils.getReferenceLink; /** * diff --git a/src/test/java/org/restheart/handlers/files/GetFileHandlerIT.java b/src/test/java/org/restheart/handlers/files/GetFileHandlerIT.java index e9178f3a43..e51743170d 100644 --- a/src/test/java/org/restheart/handlers/files/GetFileHandlerIT.java +++ b/src/test/java/org/restheart/handlers/files/GetFileHandlerIT.java @@ -18,62 +18,31 @@ package org.restheart.handlers.files; import com.mongodb.DB; -import com.mongodb.Mongo; -import com.mongodb.MongoClient; import com.mongodb.gridfs.GridFS; import com.mongodb.gridfs.GridFSInputFile; import java.io.File; import java.io.InputStream; import java.net.UnknownHostException; -import org.apache.http.HttpHost; -import org.apache.http.client.fluent.Executor; import org.apache.http.client.fluent.Request; import org.apache.http.client.fluent.Response; -import org.junit.AfterClass; -import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.restheart.test.integration.AbstactIT; /** * * @author Maurizio Turatti */ -public class GetFileHandlerIT { +public class GetFileHandlerIT extends AbstactIT { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); - public static final String HOST = "localhost"; public static final String FILENAME = "RESTHeart_documentation.pdf"; - public static final String DB_NAME = "testdb-" + System.currentTimeMillis(); - public static final String DB_URL = "http://localhost:18080/" + DB_NAME; public static final String BUCKET = "mybucket"; public static Object OID; - static Executor executor = null; - - @BeforeClass - public static void setUpClass() throws UnknownHostException { - DB db = getDatabase(); - InputStream is = GetFileHandlerIT.class.getResourceAsStream("/" + FILENAME); - GridFS gridfs = new GridFS(db, BUCKET); - GridFSInputFile gfsFile = gridfs.createFile(is); - OID = gfsFile.getId(); - gfsFile.setFilename(FILENAME); - gfsFile.save(); - - executor = Executor.newInstance() - .authPreemptive(new HttpHost(HOST, 18080, "HTTP")) - .auth(new HttpHost(HOST), "admin", "changeit"); - } - - @AfterClass - public static void afterClass() throws UnknownHostException { - DB db = getDatabase(); - db.dropDatabase(); - } public GetFileHandlerIT() { } @@ -81,18 +50,29 @@ public GetFileHandlerIT() { @Test public void testHandleRequest() throws Exception { System.out.println("testHandleRequest"); - String url = DB_URL + "/" + BUCKET + ".files/" + OID + "/binary"; + + createFile(); + + String url = dbTmpUri + "/" + BUCKET + ".files/" + OID + "/binary"; System.out.println("URL = " + url); - Response resp = executor.execute(Request.Get(url)); + Response resp = adminExecutor.execute(Request.Get(url)); File tempFile = tempFolder.newFile(FILENAME); resp.saveContent(tempFile); assertTrue(tempFile.length() > 0); } + private void createFile() throws UnknownHostException { + DB db = getDatabase(); + InputStream is = GetFileHandlerIT.class.getResourceAsStream("/" + FILENAME); + GridFS gridfs = new GridFS(db, BUCKET); + GridFSInputFile gfsFile = gridfs.createFile(is); + OID = gfsFile.getId(); + gfsFile.setFilename(FILENAME); + gfsFile.save(); + } + private static DB getDatabase() throws UnknownHostException { - Mongo mongo = new MongoClient(); - DB db = mongo.getDB(DB_NAME); - return db; + return mongoClient.getDB(dbTmpName); } } diff --git a/src/test/java/org/restheart/test/integration/AbstactIT.java b/src/test/java/org/restheart/test/integration/AbstactIT.java index 9239c6b590..040b58eff0 100644 --- a/src/test/java/org/restheart/test/integration/AbstactIT.java +++ b/src/test/java/org/restheart/test/integration/AbstactIT.java @@ -29,15 +29,18 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; +import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; import org.apache.http.StatusLine; import org.apache.http.client.fluent.Executor; import org.apache.http.client.fluent.Response; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.ContentType; +import org.apache.http.message.BasicNameValuePair; import org.bson.types.ObjectId; import org.junit.After; import org.junit.AfterClass; @@ -53,9 +56,9 @@ */ public abstract class AbstactIT { - private static final Logger LOG = LoggerFactory.getLogger(AbstactIT.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AbstactIT.class); - protected static final String HOST = "127.0.0.1"; + protected static final String CLIENT_HOST = "127.0.0.1"; protected static final String HTTP = "http"; protected static final Path confFilePath = new File("etc/restheart-integrationtest.yml").toPath(); @@ -163,7 +166,7 @@ public abstract class AbstactIT { "{ \"band\": 1 }", "{ \"ranking\": 1 }" }; - + private final Database dbsDAO = new DbsDAO(); public AbstactIT() { @@ -171,7 +174,7 @@ public AbstactIT() { @BeforeClass public static void setUpClass() throws Exception { - LOG.info("@@@ Initializing integration tests"); + LOGGER.info("@@@ setUpClass"); conf = new Configuration(confFilePath); MongoDBClientSingleton.init(conf); @@ -179,24 +182,28 @@ public static void setUpClass() throws Exception { createURIs(); - adminExecutor = Executor.newInstance().authPreemptive(new HttpHost(HOST, 8080, HTTP)).auth(new HttpHost(HOST), "admin", "changeit"); - user1Executor = Executor.newInstance().authPreemptive(new HttpHost(HOST, 8080, HTTP)).auth(new HttpHost(HOST), "user1", "changeit"); - user2Executor = Executor.newInstance().authPreemptive(new HttpHost(HOST, 8080, HTTP)).auth(new HttpHost(HOST), "user2", "changeit"); + final String host = CLIENT_HOST; + final int port = conf.getHttpPort(); + adminExecutor = Executor.newInstance().authPreemptive(new HttpHost(host, port, HTTP)).auth(new HttpHost(host), "admin", "changeit"); + user1Executor = Executor.newInstance().authPreemptive(new HttpHost(host, port, HTTP)).auth(new HttpHost(host), "user1", "changeit"); + user2Executor = Executor.newInstance().authPreemptive(new HttpHost(host, port, HTTP)).auth(new HttpHost(host), "user2", "changeit"); unauthExecutor = Executor.newInstance(); } @AfterClass public static void tearDownClass() { - LOG.info("@@@ Cleaning-up integration tests"); + LOGGER.info("@@@ tearDownClass"); } @Before public void setUp() { + LOGGER.info("setUp"); createTestData(); } @After public void tearDown() { + LOGGER.info("tearDown"); deleteTestData(); } @@ -214,7 +221,7 @@ protected HttpResponse check(String message, Response resp, int expectedCode) th private void createTestData() { dbsDAO.upsertDB(dbName, dbProps, new ObjectId(), false); - + dbsDAO.upsertCollection(dbName, collection1Name, coll1Props, new ObjectId(), false, false); dbsDAO.upsertCollection(dbName, collection2Name, coll2Props, new ObjectId(), false, false); dbsDAO.upsertCollection(dbName, docsCollectionName, docsCollectionProps, new ObjectId(), false, false); @@ -230,7 +237,7 @@ private void createTestData() { for (String doc : docsPropsStrings) { documentDAO.upsertDocument(dbName, docsCollectionName, new ObjectId().toString(), ((DBObject) JSON.parse(doc)), new ObjectId(), false); } - LOG.info("test data created"); + LOGGER.info("test data created"); } private void deleteTestData() { @@ -241,276 +248,94 @@ private void deleteTestData() { if (databases.contains(dbTmpName)) { MongoDBClientSingleton.getInstance().getClient().dropDatabase(dbTmpName); } - LOG.info("existing data deleted"); + LOGGER.info("test data deleted"); } private static void createURIs() throws URISyntaxException { - rootUri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/") - .build(); - - rootUriRemapped = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDALL) - .build(); - - dbUri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName) - .build(); - - dbUriPaging = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName) - .addParameter("pagesize", "1") - .build(); - - dbUriRemappedAll = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDALL + "/" + dbName) - .build(); - - dbUriRemappedDb = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDDB) - .build(); - - dbTmpUri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbTmpName) - .build(); - - collection1Uri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName + "/" + collection1Name) - .build(); - - collection1UriRemappedAll = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDALL + "/" + dbName + "/" + collection1Name) - .build(); - - collection1UriRemappedDb = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDDB + "/" + collection1Name) - .build(); - - collection1UriRemappedCollection = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDREFCOLL1) - .build(); - - collection2Uri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName + "/" + collection2Name) - .build(); - - collection2UriRemappedAll = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDALL + "/" + dbName + "/" + collection2Name) - .build(); - - collection2UriRemappedDb = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDDB + "/" + collection2Name) - .build(); - - collection2UriRemappedCollection = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDREFCOLL2) - .build(); - - collectionTmpUri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbTmpName + "/" + collectionTmpName) - .build(); - - docsCollectionUri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName + "/" + docsCollectionName) - .build(); - - docsCollectionUriPaging = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName + "/" + docsCollectionName) - .addParameter("pagesize", "2") - .build(); - - docsCollectionUriCountAndPaging = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName + "/" + docsCollectionName) - .addParameter("count", null) - .addParameter("page", "2") - .addParameter("pagesize", "2") - .build(); - - docsCollectionUriSort = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName + "/" + docsCollectionName) - .addParameter("sort_by", "surname") - .build(); - - docsCollectionUriFilter = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName + "/" + docsCollectionName) - .addParameter("filter", "{'name':{'$regex':'.*k$'}}") - .addParameter("sort_by", "name") - .addParameter("count", null) - .build(); - - indexesUri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName + "/" + docsCollectionName + _INDEXES) - .build(); - - indexesUriRemappedAll = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDALL + "/" + dbName + "/" + docsCollectionName + _INDEXES) - .build(); - - indexesUriRemappedDb = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDDB + "/" + docsCollectionName + _INDEXES) - .build(); - - documentTmpUri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbTmpName + "/" + collectionTmpName + "/" + documentTmpId) - .build(); - - indexesTmpUri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbTmpName + "/" + collectionTmpName + _INDEXES) - .build(); - indexTmpUri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbTmpName + "/" + collectionTmpName + _INDEXES + "/new-index") - .build(); - - document1Uri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName + "/" + collection1Name + "/" + document1Id) - .build(); - - document1UriRemappedAll = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDALL + "/" + dbName + "/" + collection1Name + "/" + document1Id) - .build(); - - document1UriRemappedDb = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDDB + "/" + collection1Name + "/" + document1Id) - .build(); - - document1UriRemappedCollection = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDREFCOLL1 + "/" + document1Id) - .build(); - - document1UriRemappedDocument = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDDOC1) - .build(); - - document2Uri = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbName + "/" + collection2Name + "/" + document2Id) - .build(); - - document2UriRemappedAll = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDALL + "/" + dbName + "/" + collection1Name + "/" + document2Id) - .build(); + rootUri = buildURI("/"); + rootUriRemapped = buildURI(REMAPPEDALL); + + dbUri = buildURI("/" + dbName); + dbUriPaging = buildURI("/" + dbName, + new NameValuePair[]{ + new BasicNameValuePair("pagesize", "1") + }); + + dbUriRemappedAll = buildURI(REMAPPEDALL + "/" + dbName); + dbUriRemappedDb = buildURI(REMAPPEDDB); + dbTmpUri = buildURI("/" + dbTmpName); + + collection1Uri = buildURI("/" + dbName + "/" + collection1Name); + collection1UriRemappedAll = buildURI(REMAPPEDALL + "/" + dbName + "/" + collection1Name); + collection1UriRemappedDb = buildURI(REMAPPEDDB + "/" + collection1Name); + collection1UriRemappedCollection = buildURI(REMAPPEDREFCOLL1); + collection2Uri = buildURI("/" + dbName + "/" + collection2Name); + collection2UriRemappedAll = buildURI(REMAPPEDALL + "/" + dbName + "/" + collection2Name); + collection2UriRemappedDb = buildURI(REMAPPEDDB + "/" + collection2Name); + collection2UriRemappedCollection = buildURI(REMAPPEDREFCOLL2); + collectionTmpUri = buildURI("/" + dbTmpName + "/" + collectionTmpName); + + docsCollectionUri = buildURI("/" + dbName + "/" + docsCollectionName); + docsCollectionUriPaging = buildURI("/" + dbName + "/" + docsCollectionName, + new NameValuePair[]{ + new BasicNameValuePair("pagesize", "2") + }); + + docsCollectionUriCountAndPaging = buildURI("/" + dbName + "/" + docsCollectionName, + new NameValuePair[]{ + new BasicNameValuePair("count", null), + new BasicNameValuePair("page", "2"), + new BasicNameValuePair("pagesize", "2") + }); + + docsCollectionUriSort = buildURI("/" + dbName + "/" + docsCollectionName, + new NameValuePair[]{ + new BasicNameValuePair("sort_by", "surname") + }); + + docsCollectionUriFilter = buildURI("/" + dbName + "/" + docsCollectionName, + new NameValuePair[]{ + new BasicNameValuePair("filter", "{'name':{'$regex':'.*k$'}}"), + new BasicNameValuePair("sort_by", "name"), + new BasicNameValuePair("count", null) + }); + + indexesUri = buildURI("/" + dbName + "/" + docsCollectionName + _INDEXES); + indexesUriRemappedAll = buildURI(REMAPPEDALL + "/" + dbName + "/" + docsCollectionName + _INDEXES); + indexesUriRemappedDb = buildURI(REMAPPEDDB + "/" + docsCollectionName + _INDEXES); + indexesTmpUri = buildURI("/" + dbTmpName + "/" + collectionTmpName + _INDEXES); + indexTmpUri = buildURI("/" + dbTmpName + "/" + collectionTmpName + _INDEXES + "/new-index"); + + documentTmpUri = buildURI("/" + dbTmpName + "/" + collectionTmpName + "/" + documentTmpId); + document1Uri = buildURI("/" + dbName + "/" + collection1Name + "/" + document1Id); + document1UriRemappedAll = buildURI(REMAPPEDALL + "/" + dbName + "/" + collection1Name + "/" + document1Id); + document1UriRemappedDb = buildURI(REMAPPEDDB + "/" + collection1Name + "/" + document1Id); + document1UriRemappedCollection = buildURI(REMAPPEDREFCOLL1 + "/" + document1Id); + document1UriRemappedDocument = buildURI(REMAPPEDDOC1); + document2Uri = buildURI("/" + dbName + "/" + collection2Name + "/" + document2Id); + document2UriRemappedAll = buildURI(REMAPPEDALL + "/" + dbName + "/" + collection1Name + "/" + document2Id); + document2UriRemappedDb = buildURI(REMAPPEDDB + "/" + collection2Name + "/" + document2Id); + document2UriRemappedCollection = buildURI(REMAPPEDREFCOLL2 + "/" + document2Id); + document2UriRemappedDocument = buildURI(REMAPPEDDOC2); + } - document2UriRemappedDb = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDDB + "/" + collection2Name + "/" + document2Id) + protected static URI buildURI(String path, NameValuePair[] parameters) throws URISyntaxException { + return createURIBuilder(path) + .addParameters(Arrays.asList(parameters)) .build(); + } - document2UriRemappedCollection = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath(REMAPPEDREFCOLL2 + "/" + document2Id) + protected static URI buildURI(String path) throws URISyntaxException { + return createURIBuilder(path) .build(); + } - document2UriRemappedDocument = new URIBuilder() + private static URIBuilder createURIBuilder(String path) { + return new URIBuilder() .setScheme(HTTP) - .setHost(HOST) + .setHost(CLIENT_HOST) .setPort(conf.getHttpPort()) - .setPath(REMAPPEDDOC2) - .build(); + .setPath(path); } private static final String _INDEXES = "/_indexes"; diff --git a/src/test/java/org/restheart/test/integration/DocIdTypeIT.java b/src/test/java/org/restheart/test/integration/DocIdTypeIT.java index 584de22342..9d8351dab9 100644 --- a/src/test/java/org/restheart/test/integration/DocIdTypeIT.java +++ b/src/test/java/org/restheart/test/integration/DocIdTypeIT.java @@ -25,9 +25,10 @@ import org.apache.http.Header; import static org.junit.Assert.*; import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; import org.apache.http.client.fluent.Request; import org.apache.http.client.fluent.Response; -import org.apache.http.client.utils.URIBuilder; +import org.apache.http.message.BasicNameValuePair; import org.junit.Test; import org.restheart.handlers.RequestContext.DOC_ID_TYPE; import static org.restheart.handlers.RequestContext.DOC_ID_TYPE_KEY; @@ -52,23 +53,26 @@ public void testPostCollectionInt() throws Exception { Response resp; // *** PUT tmpdb - resp = adminExecutor.execute(Request.Put(dbTmpUri).bodyString("{a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Put(dbTmpUri) + .bodyString("{a:1}", halCT) + .addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); check("check put db", resp, HttpStatus.SC_CREATED); // *** PUT tmpcoll - resp = adminExecutor.execute(Request.Put(collectionTmpUri).bodyString("{a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Put(collectionTmpUri) + .bodyString("{a:1}", halCT) + .addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); check("check put coll1", resp, HttpStatus.SC_CREATED); - URI collectionTmpUriInt = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbTmpName + "/" + collectionTmpName) - .setParameter(DOC_ID_TYPE_KEY, DOC_ID_TYPE.INT.name()) - .build(); + URI collectionTmpUriInt = buildURI("/" + dbTmpName + "/" + collectionTmpName, + new NameValuePair[]{ + new BasicNameValuePair(DOC_ID_TYPE_KEY, DOC_ID_TYPE.INT.name()) + }); // *** POST tmpcoll - resp = adminExecutor.execute(Request.Post(collectionTmpUriInt).bodyString("{_id:100, a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Post(collectionTmpUriInt) + .bodyString("{_id:100, a:1}", halCT) + .addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); HttpResponse httpResp = check("check post coll1 again", resp, HttpStatus.SC_CREATED); Header[] headers = httpResp.getHeaders(Headers.LOCATION_STRING); @@ -80,10 +84,10 @@ public void testPostCollectionInt() throws Exception { String location = locationH.getValue(); //assertTrue("check location header value", location.endsWith("/100?doc_id_type=INT")); - URI createdDocUri = URI.create(location); - resp = adminExecutor.execute(Request.Get(createdDocUri).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Get(createdDocUri) + .addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); assertTrue("check created doc content", content.get("_id").asInt() == 100); @@ -101,23 +105,26 @@ public void testPostCollectionString() throws Exception { Response resp; // *** PUT tmpdb - resp = adminExecutor.execute(Request.Put(dbTmpUri).bodyString("{a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Put(dbTmpUri) + .bodyString("{a:1}", halCT) + .addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); check("check put db", resp, HttpStatus.SC_CREATED); // *** PUT tmpcoll - resp = adminExecutor.execute(Request.Put(collectionTmpUri).bodyString("{a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Put(collectionTmpUri) + .bodyString("{a:1}", halCT) + .addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); check("check put coll1", resp, HttpStatus.SC_CREATED); - URI collectionTmpUriInt = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbTmpName + "/" + collectionTmpName) - .setParameter(DOC_ID_TYPE_KEY, DOC_ID_TYPE.STRING.name()) - .build(); + URI collectionTmpUriInt = buildURI("/" + dbTmpName + "/" + collectionTmpName, + new NameValuePair[]{ + new BasicNameValuePair(DOC_ID_TYPE_KEY, DOC_ID_TYPE.STRING.name()) + }); // *** POST tmpcoll - resp = adminExecutor.execute(Request.Post(collectionTmpUriInt).bodyString("{_id:{'$oid':'54c965cbc2e64568e235b711'}, a:1}", halCT).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Post(collectionTmpUriInt) + .bodyString("{_id:{'$oid':'54c965cbc2e64568e235b711'}, a:1}", halCT) + .addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); HttpResponse httpResp = check("check post coll1 again", resp, HttpStatus.SC_CREATED); Header[] headers = httpResp.getHeaders(Headers.LOCATION_STRING); @@ -129,41 +136,38 @@ public void testPostCollectionString() throws Exception { String location = locationH.getValue(); //assertTrue("check location header value", location.endsWith("/54c965cbc2e64568e235b711?doc_id_type=STRING")); - URI createdDocUri = URI.create(location); - resp = adminExecutor.execute(Request.Get(createdDocUri).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Get(createdDocUri) + .addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); JsonObject content = JsonObject.readFrom(resp.returnContent().asString()); - assertTrue("check created doc content", content.get("_id").asObject().get("$oid").asString().equals("54c965cbc2e64568e235b711")); + assertTrue("check created doc content", content.get("_id").asObject().get("$oid").asString() + .equals("54c965cbc2e64568e235b711")); assertNotNull("check created doc content", content.get("_etag")); assertNotNull("check created doc content", content.get("a")); assertTrue("check created doc content", content.get("a").asInt() == 1); // *** filter - case 1 - with string id should not find it - URI collectionTmpUriSearch = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbTmpName + "/" + collectionTmpName) - .setParameter("filter", "{'_id':'54c965cbc2e64568e235b711'}") - .build(); + URI collectionTmpUriSearch = buildURI("/" + dbTmpName + "/" + collectionTmpName, + new NameValuePair[]{ + new BasicNameValuePair("filter", "{'_id':'54c965cbc2e64568e235b711'}") + }); - resp = adminExecutor.execute(Request.Get(collectionTmpUriSearch).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + resp = adminExecutor.execute(Request.Get(collectionTmpUriSearch) + .addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); content = JsonObject.readFrom(resp.returnContent().asString()); assertTrue("check created doc content", content.get("_returned").asInt() == 0); // *** filter - case 1 - with oid id should find it - collectionTmpUriSearch = new URIBuilder() - .setScheme(HTTP) - .setHost(HOST) - .setPort(conf.getHttpPort()) - .setPath("/" + dbTmpName + "/" + collectionTmpName) - .setParameter("filter", "{'_id':{'$oid':'54c965cbc2e64568e235b711'}}") - .build(); - - resp = adminExecutor.execute(Request.Get(collectionTmpUriSearch).addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); + collectionTmpUriSearch = buildURI("/" + dbTmpName + "/" + collectionTmpName, + new NameValuePair[]{ + new BasicNameValuePair("filter", "{'_id':{'$oid':'54c965cbc2e64568e235b711'}}") + }); + + resp = adminExecutor.execute(Request.Get(collectionTmpUriSearch) + .addHeader(Headers.CONTENT_TYPE_STRING, Representation.HAL_JSON_MEDIA_TYPE)); content = JsonObject.readFrom(resp.returnContent().asString()); assertTrue("check created doc content", content.get("_returned").asInt() == 1); From 6ce44293689b1eacc34f233fab40f1b2a266622d Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Thu, 12 Feb 2015 22:04:36 +0100 Subject: [PATCH 12/17] the _created_on document property is not stored anymore. it is derived from the _id and added to the resource representation if this is an ObjectId if sort_by query paramter is _created_on or _last_updated_on, a warning message is added to the response --- .../java/org/restheart/db/CollectionDAO.java | 5 +- .../java/org/restheart/db/DocumentDAO.java | 52 +++---------------- src/main/java/org/restheart/db/GridFsDAO.java | 5 -- .../DocumentRepresentationFactory.java | 3 +- .../handlers/document/GetDocumentHandler.java | 15 ++++-- .../injectors/BodyInjectorHandler.java | 3 +- .../RequestContextInjectorHandler.java | 8 +++ .../test/integration/GetDocumentIT.java | 16 ++++-- .../test/integration/GetIndexesIT.java | 4 +- .../test/integration/PutIndexIT.java | 4 +- 10 files changed, 47 insertions(+), 68 deletions(-) diff --git a/src/main/java/org/restheart/db/CollectionDAO.java b/src/main/java/org/restheart/db/CollectionDAO.java index 72e42c9e2a..3415bddfe9 100644 --- a/src/main/java/org/restheart/db/CollectionDAO.java +++ b/src/main/java/org/restheart/db/CollectionDAO.java @@ -147,14 +147,12 @@ DBCursor getCollectionDBCursor(DBCollection coll, Deque sortBy, Deque { String _s = s.trim(); // the + sign is decoded into a space, in case remove it - _s = _s.replaceAll("_lastupdated_on", "_etag"); // _lastupdated is not stored and actually generated from @etag - if (_s.startsWith("-")) { sort.put(_s.substring(1), -1); } else if (_s.startsWith("+")) { @@ -379,6 +377,5 @@ int deleteCollection(String dbName, String collName, ObjectId etag) { private void initDefaultIndexes(DBCollection coll) { coll.createIndex(new BasicDBObject("_id", 1).append("_etag", 1), new BasicDBObject("name", "_id_etag_idx")); coll.createIndex(new BasicDBObject("_etag", 1), new BasicDBObject("name", "_etag_idx")); - coll.createIndex(new BasicDBObject("_created_on", 1), new BasicDBObject("name", "_created_on_idx")); } } \ No newline at end of file diff --git a/src/main/java/org/restheart/db/DocumentDAO.java b/src/main/java/org/restheart/db/DocumentDAO.java index 8e376ddd3e..bf9de0e70e 100644 --- a/src/main/java/org/restheart/db/DocumentDAO.java +++ b/src/main/java/org/restheart/db/DocumentDAO.java @@ -69,8 +69,6 @@ public int upsertDocument(String dbName, String collName, Object documentId, DBO BasicDBObject idQuery = new BasicDBObject("_id", documentId); if (patching) { - content.removeField("_created_on"); // it makes sure the client doesn't change this field - DBObject oldDocument = coll.findAndModify(idQuery, null, null, false, new BasicDBObject("$set", content), false, false); if (oldDocument == null) { @@ -80,8 +78,6 @@ public int upsertDocument(String dbName, String collName, Object documentId, DBO return optimisticCheckEtag(coll, oldDocument, requestEtag, HttpStatus.SC_OK); } } else { - content.put("_created_on", now.toString()); // let's assume this is an insert. in case we'll set it back with a second update - // we use findAndModify to get the @created_on field value from the existing document // in case this is an update well need to upsertDocument it back using a second update // it is not possible to do it with a single update @@ -89,20 +85,7 @@ public int upsertDocument(String dbName, String collName, Object documentId, DBO DBObject oldDocument = coll.findAndModify(idQuery, null, null, false, content, false, true); if (oldDocument != null) { // upsertDocument - Object oldTimestamp = oldDocument.get("_created_on"); - - if (oldTimestamp == null) { - oldTimestamp = now.toString(); - LOGGER.warn("properties of document /{}/{}/{} had no @created_on field. set it to current time", - dbName, collName, documentId); - } - - // need to readd the @created_on field - BasicDBObject created = new BasicDBObject("_created_on", "" + oldTimestamp); - created.markAsPartialObject(); - coll.update(idQuery, new BasicDBObject("$set", created), true, false); - - // check the old etag (in case restore the old document version) + // check the old etag (in case restore the old document) return optimisticCheckEtag(coll, oldDocument, requestEtag, HttpStatus.SC_OK); } else { // insert return HttpStatus.SC_CREATED; @@ -125,14 +108,12 @@ public int upsertDocumentPost(String dbName, String collName, Object documentId, DBCollection coll = db.getCollection(collName); ObjectId timestamp = new ObjectId(); - Instant now = Instant.ofEpochSecond(timestamp.getTimestamp()); if (content == null) { content = new BasicDBObject(); } content.put("_etag", timestamp); - content.put("_created_on", now.toString()); // it makes sure the client doesn't change this field Object _idInContent = content.get("_id"); @@ -149,27 +130,9 @@ public int upsertDocumentPost(String dbName, String collName, Object documentId, BasicDBObject idQuery = new BasicDBObject("_id", documentId); - // we use findAndModify to get the _created_on field value from the existing document - // we need to upsertDocument this field back using a second update - // it is not possible in a single update even using $setOnInsert update operator - // in this case we need to provide the other data using $set operator and this makes it a partial update (patch semantic) DBObject oldDocument = coll.findAndModify(idQuery, null, null, false, content, false, true); - if (oldDocument - != null) { // upsertDocument - Object oldTimestamp = oldDocument.get("_created_on"); - - if (oldTimestamp == null) { - oldTimestamp = now.toString(); - LOGGER.warn("properties of document /{}/{}/{} had no @created_on field. set it to current time", - dbName, collName, _idInContent.toString()); - } - - // need to readd the @created_on field - BasicDBObject createdContent = new BasicDBObject("_created_on", "" + oldTimestamp); - createdContent.markAsPartialObject(); - coll.update(idQuery, new BasicDBObject("$set", createdContent), true, false); - + if (oldDocument != null) { // upsertDocument // check the old etag (in case restore the old document version) return optimisticCheckEtag(coll, oldDocument, requestEtag, HttpStatus.SC_OK); } else { // insert @@ -185,7 +148,8 @@ public int upsertDocumentPost(String dbName, String collName, Object documentId, * @return */ @Override - public int deleteDocument(String dbName, String collName, Object documentId, ObjectId requestEtag) { + public int deleteDocument(String dbName, String collName, Object documentId, ObjectId requestEtag + ) { DB db = client.getDB(dbName); DBCollection coll = db.getCollection(collName); @@ -204,15 +168,15 @@ public int deleteDocument(String dbName, String collName, Object documentId, Obj private int optimisticCheckEtag(DBCollection coll, DBObject oldDocument, ObjectId requestEtag, int httpStatusIfOk) { Object oldEtag = oldDocument.get("_etag"); - + if (oldEtag == null) { // well we don't had an etag there so fine return httpStatusIfOk; } - + if (!(oldEtag instanceof ObjectId)) { // well the _etag is not an ObjectId. no check is possible return httpStatusIfOk; } - + if (requestEtag == null) { coll.save(oldDocument); return HttpStatus.SC_CONFLICT; @@ -227,4 +191,4 @@ private int optimisticCheckEtag(DBCollection coll, DBObject oldDocument, ObjectI return HttpStatus.SC_PRECONDITION_FAILED; } } -} \ No newline at end of file +} diff --git a/src/main/java/org/restheart/db/GridFsDAO.java b/src/main/java/org/restheart/db/GridFsDAO.java index 7bdc46c88a..6b53e56acb 100644 --- a/src/main/java/org/restheart/db/GridFsDAO.java +++ b/src/main/java/org/restheart/db/GridFsDAO.java @@ -46,8 +46,6 @@ public int createFile(Database db, String dbName, String bucketName, Object file GridFS gridfs = new GridFS(db.getDB(dbName), bucket); GridFSInputFile gfsFile = gridfs.createFile(data); - ObjectId now = new ObjectId(); - // remove from the properties the fields that are managed directly by the GridFs properties.removeField("_id"); Object _fileName = properties.removeField("filename"); @@ -63,9 +61,6 @@ public int createFile(Database db, String dbName, String bucketName, Object file fileName = null; } - // it makes sure the client doesn't change this field - properties.put("_created_on", now); - // add etag properties.put("_etag", new ObjectId()); diff --git a/src/main/java/org/restheart/handlers/document/DocumentRepresentationFactory.java b/src/main/java/org/restheart/handlers/document/DocumentRepresentationFactory.java index b929c99dbd..bdc4dfb023 100644 --- a/src/main/java/org/restheart/handlers/document/DocumentRepresentationFactory.java +++ b/src/main/java/org/restheart/handlers/document/DocumentRepresentationFactory.java @@ -30,6 +30,7 @@ import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import java.net.URISyntaxException; +import java.time.Instant; import java.util.List; import java.util.TreeMap; import org.bson.types.ObjectId; @@ -88,7 +89,7 @@ public static Representation getDocument(String href, HttpServerExchange exchang rep = new Representation(href.concat("?").concat(_docIdType)); } } - + rep.addProperty("_type", context.getType().name()); // document properties diff --git a/src/main/java/org/restheart/handlers/document/GetDocumentHandler.java b/src/main/java/org/restheart/handlers/document/GetDocumentHandler.java index 3ccdfd841c..248ebe071f 100644 --- a/src/main/java/org/restheart/handlers/document/GetDocumentHandler.java +++ b/src/main/java/org/restheart/handlers/document/GetDocumentHandler.java @@ -62,16 +62,25 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t Object etag = document.get("_etag"); if (etag != null && etag instanceof ObjectId) { - // add the _lastupdated_on in case the _etag field is present and its value is an ObjectId - document.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId)etag).getTimestamp()).toString()); + if (document.get("_lastupdated_on") == null) { + // add the _lastupdated_on in case the _etag field is present and its value is an ObjectId + document.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId) etag).getTimestamp()).toString()); + } // in case the request contains the IF_NONE_MATCH header with the current etag value, // just return 304 NOT_MODIFIED code - if (RequestHelper.checkReadEtag(exchange, (ObjectId)etag)) { + if (RequestHelper.checkReadEtag(exchange, (ObjectId) etag)) { ResponseHelper.endExchange(exchange, HttpStatus.SC_NOT_MODIFIED); return; } } + + Object id = document.get("_id"); + + // generate the _created_on timestamp from the _id if this is an instance of ObjectId + if (document.get("_created_on") == null && id != null && id instanceof ObjectId) { + document.put("_created_on", Instant.ofEpochSecond(((ObjectId)id).getTimestamp()).toString()); + } String requestPath = URLUtils.removeTrailingSlashes(exchange.getRequestPath()); diff --git a/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java b/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java index 106a33a68c..f607b84e37 100644 --- a/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java +++ b/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java @@ -30,7 +30,6 @@ import io.undertow.util.HeaderValues; import io.undertow.util.Headers; import java.util.HashSet; -import org.restheart.hal.HALUtils; /** * @@ -78,7 +77,7 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t if (isNotFormData(contentTypes)) { final String contentString = ChannelReader.read(exchange.getRequestChannel()); - DBObject content = null; + DBObject content; try { content = (DBObject) JSON.parse(contentString); } catch (JSONParseException | IllegalArgumentException ex) { diff --git a/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java b/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java index 2a69873c1e..e4cbc72e6c 100644 --- a/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java +++ b/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java @@ -137,6 +137,14 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t "illegal sort_by paramenter"); return; } + + if (sort_by.stream().anyMatch(s -> s.trim().equals("_last_updated_on") || s.trim().equals("+_last_updated_on") || s.trim().equals("-_last_updated_on") )) { + rcontext.addWarning("unexepecting sorting; the _last_updated_on timestamp is generated from the _etag property if present"); + } + + if (sort_by.stream().anyMatch(s -> s.trim().equals("_created_on") ||s.trim().equals("_created_on") || s.trim().equals("_created_on"))) { + rcontext.addWarning("unexepecting sorting; the _created_on timestamp is generated from the _id property if it is an ObjectId"); + } rcontext.setSortBy(exchange.getQueryParameters().get(SORT_BY_QPARAM_KEY)); } diff --git a/src/test/java/org/restheart/test/integration/GetDocumentIT.java b/src/test/java/org/restheart/test/integration/GetDocumentIT.java index fde0b93a7e..b4c1952430 100644 --- a/src/test/java/org/restheart/test/integration/GetDocumentIT.java +++ b/src/test/java/org/restheart/test/integration/GetDocumentIT.java @@ -29,6 +29,7 @@ import org.apache.http.client.fluent.Response; import org.apache.http.client.utils.URIBuilder; import org.apache.http.util.EntityUtils; +import org.bson.types.ObjectId; import org.junit.Test; /** @@ -87,11 +88,16 @@ private void testGetDocument(URI uri) throws Exception { } assertNotNull("check json not null", json); - assertNotNull("check not null @etag property", json.get("_etag")); - assertNotNull("check not null @lastupdated_on property", json.get("_lastupdated_on")); - assertNotNull("check not null @created_on property", json.get("_created_on")); - assertNotNull("check not null @_id property", json.get("_id")); - assertEquals("check @_id value", document1Id, json.get("_id").asString()); + assertNotNull("check not null _etag property", json.get("_etag")); + assertNotNull("check not null _lastupdated_on property", json.get("_lastupdated_on")); + + if (ObjectId.isValid(json.get("_id").asString())) + assertNotNull("check not null _created_on property", json.get("_created_on")); + else + assertNull("check null _created_on property", json.get("_created_on")); + + assertNotNull("check not null _id property", json.get("_id")); + assertEquals("check _id value", document1Id, json.get("_id").asString()); assertNotNull("check not null a", json.get("a")); assertEquals("check a value", 1, json.get("a").asInt()); assertNotNull("check not null mtm links", json.get("_links").asObject().get("mtm")); diff --git a/src/test/java/org/restheart/test/integration/GetIndexesIT.java b/src/test/java/org/restheart/test/integration/GetIndexesIT.java index 1698ea19fa..50503c95ff 100644 --- a/src/test/java/org/restheart/test/integration/GetIndexesIT.java +++ b/src/test/java/org/restheart/test/integration/GetIndexesIT.java @@ -84,8 +84,8 @@ private void testGetIndexes(URI uri) throws Exception { assertNotNull("check json not null", json); assertNotNull("check not null _returned property", json.get("_returned")); assertNotNull("check not null _size property", json.get("_size")); - assertEquals("check _size value to be 8", 8, json.get("_size").asInt()); - assertEquals("check _returned value to be 8", 8, json.get("_returned").asInt()); + assertEquals("check _size value to be 7", 7, json.get("_size").asInt()); + assertEquals("check _returned value to be 7", 7, json.get("_returned").asInt()); assertNotNull("check not null _link", json.get("_links")); assertTrue("check _link to be a json object", (json.get("_links") instanceof JsonObject)); diff --git a/src/test/java/org/restheart/test/integration/PutIndexIT.java b/src/test/java/org/restheart/test/integration/PutIndexIT.java index e35a854f76..b29951a9bc 100644 --- a/src/test/java/org/restheart/test/integration/PutIndexIT.java +++ b/src/test/java/org/restheart/test/integration/PutIndexIT.java @@ -90,8 +90,8 @@ public void testPutDocument() throws Exception { assertNotNull("check json not null", json); assertNotNull("check not null _returned property", json.get("_returned")); assertNotNull("check not null _size property", json.get("_size")); - assertEquals("check _size value to be 5", 5, json.get("_size").asInt()); - assertEquals("check _returned value to be 5", 5, json.get("_returned").asInt()); + assertEquals("check _size value to be 4", 4, json.get("_size").asInt()); + assertEquals("check _returned value to be 4", 4, json.get("_returned").asInt()); } finally { mongoClient.dropDatabase(dbTmpName); } From e1ca93450bae28af50542a1fd4f8f82e43650fde Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Thu, 12 Feb 2015 22:14:13 +0100 Subject: [PATCH 13/17] the _created_on document property added on _embedded docs also on GET /db/collection --- .../java/org/restheart/db/CollectionDAO.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/restheart/db/CollectionDAO.java b/src/main/java/org/restheart/db/CollectionDAO.java index 3415bddfe9..2da0a4343a 100644 --- a/src/main/java/org/restheart/db/CollectionDAO.java +++ b/src/main/java/org/restheart/db/CollectionDAO.java @@ -216,12 +216,19 @@ ArrayList getCollectionData( pagesize--; } - // add the _lastupdated_on in case the _etag field is present and its value is an ObjectId + // add the _lastupdated_on and _created_on ret.forEach(row -> { Object etag = row.get("_etag"); - if (etag != null && etag instanceof ObjectId) { - row.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId)etag).getTimestamp()).toString()); + if (row.get("_lastupdated_on") == null && etag != null && etag instanceof ObjectId) { + row.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId) etag).getTimestamp()).toString()); + } + + Object id = row.get("_id"); + + // generate the _created_on timestamp from the _id if this is an instance of ObjectId + if (row.get("_created_on") == null && id != null && id instanceof ObjectId) { + row.put("_created_on", Instant.ofEpochSecond(((ObjectId) id).getTimestamp()).toString()); } } ); @@ -247,7 +254,7 @@ public DBObject getCollectionProps(String dbName, String collName, boolean fixMi Object etag = properties.get("_etag"); if (etag != null && etag instanceof ObjectId) { - properties.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId)etag).getTimestamp()).toString()); + properties.put("_lastupdated_on", Instant.ofEpochSecond(((ObjectId) etag).getTimestamp()).toString()); } } else if (fixMissingProperties) { new PropsFixer().addCollectionProps(dbName, collName); @@ -378,4 +385,4 @@ private void initDefaultIndexes(DBCollection coll) { coll.createIndex(new BasicDBObject("_id", 1).append("_etag", 1), new BasicDBObject("name", "_id_etag_idx")); coll.createIndex(new BasicDBObject("_etag", 1), new BasicDBObject("name", "_etag_idx")); } -} \ No newline at end of file +} From cc56de1ab352a117702a3b944d674149b0ad951e Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Fri, 13 Feb 2015 10:37:46 +0100 Subject: [PATCH 14/17] doc_id_type query parameter renamed to id_type added support for the following id types: STRING_OID, OID, STRING, DATNUMBER, DATE, MINKEY and MAXKEY ** bugfix: resource URI was failing to build for string ids with spaces --- src/main/java/org/restheart/hal/HALUtils.java | 2 - .../restheart/handlers/RequestContext.java | 21 ++- .../collection/PostCollectionHandler.java | 2 +- .../DocumentRepresentationFactory.java | 40 +--- .../RequestContextInjectorHandler.java | 4 +- .../java/org/restheart/utils/URLUtils.java | 172 ++++++++++-------- .../test/integration/DocIdTypeIT.java | 6 +- .../org/restheart/utils/URLUtilisTest.java | 4 +- 8 files changed, 128 insertions(+), 123 deletions(-) diff --git a/src/main/java/org/restheart/hal/HALUtils.java b/src/main/java/org/restheart/hal/HALUtils.java index e32cd44b09..4ffbbcb5b5 100644 --- a/src/main/java/org/restheart/hal/HALUtils.java +++ b/src/main/java/org/restheart/hal/HALUtils.java @@ -22,8 +22,6 @@ import org.restheart.utils.URLUtils; import io.undertow.server.HttpServerExchange; import java.util.TreeMap; -import org.bson.BSONObject; -import org.bson.types.ObjectId; /** * diff --git a/src/main/java/org/restheart/handlers/RequestContext.java b/src/main/java/org/restheart/handlers/RequestContext.java index 7a4134e355..9cd522984a 100644 --- a/src/main/java/org/restheart/handlers/RequestContext.java +++ b/src/main/java/org/restheart/handlers/RequestContext.java @@ -62,8 +62,13 @@ public enum METHOD { }; public enum DOC_ID_TYPE { - - INT, LONG, FLOAT, DOUBLE, STRING, OBJECTID, STRING_OBJECTID + OID, // ObjectId + STRING_OID, // String eventually converted to ObjectId in case ObjectId.isValid() is true + STRING, // String + NUMBER, // any Number (including mongodb NumberLong) + DATE, // Date + MINKEY, //org.bson.types.MinKey; + MAXKEY // org.bson.types.MaxKey } public static final String PAGE_QPARAM_KEY = "page"; @@ -72,7 +77,7 @@ public enum DOC_ID_TYPE { public static final String SORT_BY_QPARAM_KEY = "sort_by"; public static final String FILTER_QPARAM_KEY = "filter"; public static final String EAGER_CURSOR_ALLOCATION_POLICY_QPARAM_KEY = "eager"; - public static final String DOC_ID_TYPE_KEY = "doc_id_type"; + public static final String DOC_ID_TYPE_KEY = "id_type"; public static final String SLASH = "/"; public static final String PATCH = "PATCH"; public static final String UNDERSCORE = "_"; @@ -83,6 +88,9 @@ public enum DOC_ID_TYPE { public static final String FS_FILES_SUFFIX = ".files"; public static final String _INDEXES = "_indexes"; public static final String BINARY_CONTENT = "binary"; + + public static final String MAX_KEY_ID = "_MaxKey"; + public static final String MIN_KEY_ID = "_MinKey"; private final String whereUri; private final String whatUri; @@ -104,7 +112,7 @@ public enum DOC_ID_TYPE { private EAGER_CURSOR_ALLOCATION_POLICY cursorAllocationPolicy; private Deque filter = null; private Deque sortBy = null; - private DOC_ID_TYPE docIdType = DOC_ID_TYPE.STRING_OBJECTID; + private DOC_ID_TYPE docIdType = DOC_ID_TYPE.STRING_OID; private Object documentId; private String unmappedRequestUri = null; @@ -362,7 +370,10 @@ public static boolean isReservedResourceDocument(String documentIdRaw) { return false; } - return documentIdRaw.startsWith(UNDERSCORE) && !documentIdRaw.equals(_INDEXES); + return documentIdRaw.startsWith(UNDERSCORE) && + !documentIdRaw.equalsIgnoreCase(_INDEXES) && + !documentIdRaw.equalsIgnoreCase(MIN_KEY_ID) && + !documentIdRaw.equalsIgnoreCase(MAX_KEY_ID); } /** diff --git a/src/main/java/org/restheart/handlers/collection/PostCollectionHandler.java b/src/main/java/org/restheart/handlers/collection/PostCollectionHandler.java index defb78255b..0462a55efa 100644 --- a/src/main/java/org/restheart/handlers/collection/PostCollectionHandler.java +++ b/src/main/java/org/restheart/handlers/collection/PostCollectionHandler.java @@ -83,7 +83,7 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t Object docId; if (content.get("_id") == null) { - if (context.getDocIdType() == DOC_ID_TYPE.OBJECTID || context.getDocIdType() == DOC_ID_TYPE.STRING_OBJECTID) { + if (context.getDocIdType() == DOC_ID_TYPE.OID || context.getDocIdType() == DOC_ID_TYPE.STRING_OID) { docId = new ObjectId(); } else { ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_NOT_ACCEPTABLE, "_id in content body is mandatory for documents with id type " + context.getDocIdType().name()); diff --git a/src/main/java/org/restheart/handlers/document/DocumentRepresentationFactory.java b/src/main/java/org/restheart/handlers/document/DocumentRepresentationFactory.java index bdc4dfb023..da8e34e28d 100644 --- a/src/main/java/org/restheart/handlers/document/DocumentRepresentationFactory.java +++ b/src/main/java/org/restheart/handlers/document/DocumentRepresentationFactory.java @@ -30,9 +30,9 @@ import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import java.net.URISyntaxException; -import java.time.Instant; import java.util.List; import java.util.TreeMap; +import jdk.nashorn.internal.runtime.URIUtils; import org.bson.types.ObjectId; import org.restheart.handlers.RequestContext.DOC_ID_TYPE; import org.restheart.utils.UnsupportedDocumentIdException; @@ -64,43 +64,22 @@ public static Representation getDocument(String href, HttpServerExchange exchang String _docIdType = null; - if (id == null) { - rep = new Representation("#"); - rep.addWarning("this resource does not have an URI since it does not have the _id property"); - } else if (id instanceof String || id instanceof ObjectId) { - rep = new Representation(href); - } else { - if (id instanceof Integer) { - _docIdType = "doc_id_type=" + DOC_ID_TYPE.INT; - } else if (id instanceof Long) { - _docIdType = "doc_id_type=" + DOC_ID_TYPE.LONG; - } else if (id instanceof Float) { - _docIdType = "doc_id_type=" + DOC_ID_TYPE.FLOAT; - } else if (id instanceof Double) { - _docIdType = "doc_id_type=" + DOC_ID_TYPE.DOUBLE; - } else { - _docIdType = null; - } + rep = new Representation(URLUtils.getReferenceLink(context, URLUtils.getParentPath(href), id)); - if (_docIdType == null) { - rep = new Representation("#"); - rep.addWarning("this resource does not have an URI since the _id is of type " + id.getClass().getSimpleName()); - } else { - rep = new Representation(href.concat("?").concat(_docIdType)); - } - } - - rep.addProperty("_type", context.getType().name()); + rep.addProperty( + "_type", context.getType().name()); // document properties - data.keySet().stream().forEach((key) -> rep.addProperty(key, data.get(key))); + data.keySet() + .stream().forEach((key) -> rep.addProperty(key, data.get(key))); // document links TreeMap links; links = getRelationshipsLinks(rep, context, data); - if (links != null) { + if (links + != null) { links.keySet().stream().forEach((k) -> { rep.addLink(new Link(k, links.get(k))); }); @@ -124,7 +103,8 @@ public static Representation getDocument(String href, HttpServerExchange exchang rep.addLink(new Link("rh:coll", URLUtils.getParentPath(requestPath))); } - rep.addLink(new Link("rh", "curies", Configuration.RESTHEART_ONLINE_DOC_URL + "/#api-doc-{rel}", false), true); + rep.addLink( + new Link("rh", "curies", Configuration.RESTHEART_ONLINE_DOC_URL + "/#api-doc-{rel}", false), true); return rep; } diff --git a/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java b/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java index e4cbc72e6c..d722203ca1 100644 --- a/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java +++ b/src/main/java/org/restheart/handlers/injectors/RequestContextInjectorHandler.java @@ -208,7 +208,7 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t Deque __docIdType = exchange.getQueryParameters().get(DOC_ID_TYPE_KEY); // default value - DOC_ID_TYPE docIdType = DOC_ID_TYPE.STRING_OBJECTID; + DOC_ID_TYPE docIdType = DOC_ID_TYPE.STRING_OID; if (__docIdType != null && !__docIdType.isEmpty()) { String _docIdType = __docIdType.getFirst(); @@ -232,7 +232,7 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t try { rcontext.setDocumentId(URLUtils.getId(_docId, docIdType)); } catch(UnsupportedDocumentIdException idide) { - ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_BAD_REQUEST, "wrong document id format. it is not a valid " + docIdType.name()); + ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_BAD_REQUEST, "wrong document id format: not a valid " + docIdType.name(), idide); return; } diff --git a/src/main/java/org/restheart/utils/URLUtils.java b/src/main/java/org/restheart/utils/URLUtils.java index 61b8d5cada..0c3b477eee 100644 --- a/src/main/java/org/restheart/utils/URLUtils.java +++ b/src/main/java/org/restheart/utils/URLUtils.java @@ -20,20 +20,17 @@ import org.restheart.handlers.RequestContext; import io.undertow.server.HttpServerExchange; import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URLDecoder; +import java.text.DateFormat; import java.util.Arrays; +import java.util.Date; import java.util.Deque; +import org.bson.types.MaxKey; +import org.bson.types.MinKey; import org.bson.types.ObjectId; import org.restheart.handlers.RequestContext.DOC_ID_TYPE; -import static org.restheart.handlers.RequestContext.DOC_ID_TYPE.DOUBLE; -import static org.restheart.handlers.RequestContext.DOC_ID_TYPE.FLOAT; -import static org.restheart.handlers.RequestContext.DOC_ID_TYPE.INT; -import static org.restheart.handlers.RequestContext.DOC_ID_TYPE.LONG; -import static org.restheart.handlers.RequestContext.DOC_ID_TYPE.OBJECTID; import static org.restheart.handlers.RequestContext.DOC_ID_TYPE.STRING; -import static org.restheart.handlers.RequestContext.DOC_ID_TYPE.STRING_OBJECTID; +import static org.restheart.handlers.RequestContext.DOC_ID_TYPE_KEY; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,41 +40,40 @@ */ public class URLUtils { private static final Logger LOGGER = LoggerFactory.getLogger(URLUtils.class); - + public static String getReferenceLink(RequestContext context, String parentUrl, Object docId) { if (context == null || parentUrl == null || docId == null) { LOGGER.error("error creating URI, null arguments: context = {}, parentUrl = {}, docId = {}", context, parentUrl, docId); return ""; } - - try { - URI uri; - - if (docId instanceof String && ObjectId.isValid((String)docId)) { - uri = new URI(URLUtils.removeTrailingSlashes(parentUrl) + "/" + docId.toString()+ "?doc_id_type=" + DOC_ID_TYPE.STRING); - } else if (docId instanceof String || docId instanceof ObjectId) { - uri = new URI(URLUtils.removeTrailingSlashes(parentUrl) + "/" + docId.toString()); - } else if (docId instanceof Integer) { - uri = new URI(URLUtils.removeTrailingSlashes(parentUrl) + "/" + docId.toString() + "?doc_id_type=" + DOC_ID_TYPE.INT); - } else if (docId instanceof Long) { - uri = new URI(URLUtils.removeTrailingSlashes(parentUrl) + "/" + docId.toString() + "?doc_id_type=" + DOC_ID_TYPE.LONG); - } else if (docId instanceof Float) { - uri = new URI(URLUtils.removeTrailingSlashes(parentUrl) + "/" + docId.toString() + "?doc_id_type=" + DOC_ID_TYPE.FLOAT); - } else if (docId instanceof Double) { - uri = new URI(URLUtils.removeTrailingSlashes(parentUrl) + "/" + docId.toString() + "?doc_id_type=" + DOC_ID_TYPE.DOUBLE); - } else { - context.addWarning("this resource does not have an URI since the _id is of type " + docId.getClass().getSimpleName()); - return ""; - } - return uri.toString(); - } catch (URISyntaxException ex) { - LOGGER.error("error creating URI from {} + / + {}", parentUrl, docId, ex); + String uri = "#"; + + if (docId instanceof String && ObjectId.isValid((String) docId)) { + uri = URLUtils.removeTrailingSlashes(parentUrl).concat("/").concat(docId.toString()).concat("?").concat(DOC_ID_TYPE_KEY).concat("=").concat(DOC_ID_TYPE.STRING.name()); + } else if (docId instanceof String || docId instanceof ObjectId) { + uri = URLUtils.removeTrailingSlashes(parentUrl).concat("/").concat(docId.toString()); + } else if (docId instanceof Integer) { + uri = URLUtils.removeTrailingSlashes(parentUrl).concat("/").concat(docId.toString()).concat("?").concat(DOC_ID_TYPE_KEY).concat("=").concat(DOC_ID_TYPE.NUMBER.name()); + } else if (docId instanceof Long) { + uri = URLUtils.removeTrailingSlashes(parentUrl).concat("/").concat(docId.toString()).concat("?").concat(DOC_ID_TYPE_KEY).concat("=").concat(DOC_ID_TYPE.NUMBER.name()); + } else if (docId instanceof Float) { + uri = URLUtils.removeTrailingSlashes(parentUrl).concat("/").concat(docId.toString()).concat("?").concat(DOC_ID_TYPE_KEY).concat("=").concat(DOC_ID_TYPE.NUMBER.name()); + } else if (docId instanceof Double) { + uri = URLUtils.removeTrailingSlashes(parentUrl).concat("/").concat(docId.toString()).concat("?").concat(DOC_ID_TYPE_KEY).concat("=").concat(DOC_ID_TYPE.NUMBER.name()); + } else if (docId instanceof MinKey) { + uri = URLUtils.removeTrailingSlashes(parentUrl).concat("/").concat("_MinKey"); + } else if (docId instanceof MaxKey) { + uri = URLUtils.removeTrailingSlashes(parentUrl).concat("/").concat("_MaxKey"); + } else if (docId instanceof Date) { + uri = URLUtils.removeTrailingSlashes(parentUrl).concat("/").concat(((Date) docId).getTime() + "").concat("?").concat(DOC_ID_TYPE_KEY).concat("=").concat(DOC_ID_TYPE.DATE.name()); + } else { + context.addWarning("this resource does not have an URI since the _id is of type " + docId.getClass().getSimpleName()); } - return ""; + return uri; } - + public static DOC_ID_TYPE checkId(Object id) throws UnsupportedDocumentIdException { if (id == null) { return null; @@ -87,49 +83,63 @@ public static DOC_ID_TYPE checkId(Object id) throws UnsupportedDocumentIdExcepti switch (clazz) { case "java.lang.String": - return DOC_ID_TYPE.STRING_OBJECTID; + return DOC_ID_TYPE.STRING_OID; case "org.bson.types.ObjectId": - return DOC_ID_TYPE.OBJECTID; + return DOC_ID_TYPE.OID; case "java.lang.Integer": - return DOC_ID_TYPE.INT; + return DOC_ID_TYPE.NUMBER; case "java.lang.Long": - return DOC_ID_TYPE.LONG; + return DOC_ID_TYPE.NUMBER; case "java.lang.Float": - return DOC_ID_TYPE.FLOAT; + return DOC_ID_TYPE.NUMBER; case "java.lang.Double": - return DOC_ID_TYPE.DOUBLE; + return DOC_ID_TYPE.NUMBER; + case "java.util.Date": + return DOC_ID_TYPE.DATE; + case "org.bson.types.MaxKey": + return DOC_ID_TYPE.MAXKEY; + case "org.bson.types.MinKey": + return DOC_ID_TYPE.MINKEY; default: throw new UnsupportedDocumentIdException("unknown _id type: " + id.getClass().getSimpleName()); } } - public static Object getId(Object id, DOC_ID_TYPE type) throws UnsupportedDocumentIdException { + public static Object getId(String id, DOC_ID_TYPE type) throws UnsupportedDocumentIdException { if (id == null) { return null; } if (type == null) { - type = DOC_ID_TYPE.STRING_OBJECTID; + type = DOC_ID_TYPE.STRING_OID; } - String _id = id.toString(); + // MaxKey can be also determined from the _id + if (RequestContext.MAX_KEY_ID.equalsIgnoreCase(id)) { + return new MaxKey(); + } + + // MaxKey can be also determined from the _id + if (RequestContext.MIN_KEY_ID.equalsIgnoreCase(id)) { + return new MinKey(); + } try { switch (type) { - case STRING_OBJECTID: - return getIdAsStringOrObjectId(_id); - case OBJECTID: - return getIdAsObjectId(_id); + case STRING_OID: + return getIdAsStringOrObjectId(id); + case OID: + return getIdAsObjectId(id); case STRING: return id; - case INT: - return getIdAsInt(_id); - case LONG: - return getIdAsLong(_id); - case FLOAT: - return getIdAsFloat(_id); - case DOUBLE: - return getIdAsDouble(_id); + case NUMBER: + return getIdAsNumber(id); + case MINKEY: + return new MinKey(); + case MAXKEY: + return new MaxKey(); + case DATE: + return getIdAsDate(id); } } catch (IllegalArgumentException iar) { throw new UnsupportedDocumentIdException(iar); @@ -218,10 +228,10 @@ static public String getUriWithDocId(RequestContext context, String dbName, Stri sb.append("/").append(dbName).append("/").append(collName).append("/").append(id); - if (!detectOids && docIdType == STRING_OBJECTID && ObjectId.isValid(id.toString())) { - sb.append("?doc_id_type=STRING"); - } else if (docIdType != STRING && docIdType != OBJECTID && docIdType != STRING_OBJECTID) { - sb.append("?doc_id_type=").append(docIdType.name()); + if (!detectOids && docIdType == DOC_ID_TYPE.STRING_OID && ObjectId.isValid(id.toString())) { + sb.append("?").append(DOC_ID_TYPE_KEY).append("=STRING"); + } else if (docIdType != STRING && docIdType != DOC_ID_TYPE.OID && docIdType != DOC_ID_TYPE.STRING_OID) { + sb.append("?").append(DOC_ID_TYPE_KEY).append("=").append(docIdType.name()); } return context.mapUri(sb.toString().replaceAll(" ", "")); @@ -331,36 +341,42 @@ public static String getQueryStringRemovingParams(HttpServerExchange exchange, S return ret; } - private static int getIdAsInt(String id) throws IllegalArgumentException { + private static Number getIdAsNumber(String id) throws IllegalArgumentException { try { return Integer.parseInt(id, 10); } catch (NumberFormatException nfe) { - throw new IllegalArgumentException("The id is not a valid int " + id, nfe); + try { + return Long.parseLong(id, 10); + } catch (NumberFormatException nfe2) { + try { + return Float.parseFloat(id); + } catch (NumberFormatException nfe3) { + try { + return Double.parseDouble(id); + } catch (NumberFormatException nfe4) { + try { + return Float.parseFloat(id); + } catch (NumberFormatException nfe5) { + throw new IllegalArgumentException("The id is not a valid number " + id, nfe); + } + } + } + } } } - private static long getIdAsLong(String id) throws IllegalArgumentException { - try { - return Long.parseLong(id, 10); - } catch (NumberFormatException nfe) { - throw new IllegalArgumentException("The id is not a valid long " + id, nfe); - } - } + private static Date getIdAsDate(String id) throws IllegalArgumentException { + Date ret; - private static float getIdAsFloat(String id) throws IllegalArgumentException { try { - return Float.parseFloat(id); - } catch (NumberFormatException nfe) { - throw new IllegalArgumentException("The id is not a valid float " + id, nfe); - } - } + Long n = Long.parseLong(id, 10); - private static double getIdAsDouble(String id) throws IllegalArgumentException { - try { - return Double.parseDouble(id); + ret = new Date(n); } catch (NumberFormatException nfe) { - throw new IllegalArgumentException("The id is not a valid double " + id, nfe); + throw new IllegalArgumentException("The id is not a valid date (number of milliseconds since the epoch) " + id, nfe); } + + return ret; } private static ObjectId getIdAsObjectId(String id) throws IllegalArgumentException { diff --git a/src/test/java/org/restheart/test/integration/DocIdTypeIT.java b/src/test/java/org/restheart/test/integration/DocIdTypeIT.java index 9d8351dab9..c92cae17b7 100644 --- a/src/test/java/org/restheart/test/integration/DocIdTypeIT.java +++ b/src/test/java/org/restheart/test/integration/DocIdTypeIT.java @@ -66,7 +66,7 @@ public void testPostCollectionInt() throws Exception { URI collectionTmpUriInt = buildURI("/" + dbTmpName + "/" + collectionTmpName, new NameValuePair[]{ - new BasicNameValuePair(DOC_ID_TYPE_KEY, DOC_ID_TYPE.INT.name()) + new BasicNameValuePair(DOC_ID_TYPE_KEY, DOC_ID_TYPE.NUMBER.name()) }); // *** POST tmpcoll @@ -83,7 +83,7 @@ public void testPostCollectionInt() throws Exception { Header locationH = headers[0]; String location = locationH.getValue(); - //assertTrue("check location header value", location.endsWith("/100?doc_id_type=INT")); + //assertTrue("check location header value", location.endsWith("/100?id_type=NUMBER")); URI createdDocUri = URI.create(location); resp = adminExecutor.execute(Request.Get(createdDocUri) @@ -135,7 +135,7 @@ public void testPostCollectionString() throws Exception { Header locationH = headers[0]; String location = locationH.getValue(); - //assertTrue("check location header value", location.endsWith("/54c965cbc2e64568e235b711?doc_id_type=STRING")); + //assertTrue("check location header value", location.endsWith("/54c965cbc2e64568e235b711?id_type=STRING")); URI createdDocUri = URI.create(location); resp = adminExecutor.execute(Request.Get(createdDocUri) diff --git a/src/test/java/org/restheart/utils/URLUtilisTest.java b/src/test/java/org/restheart/utils/URLUtilisTest.java index bce398a5c8..7202190c91 100644 --- a/src/test/java/org/restheart/utils/URLUtilisTest.java +++ b/src/test/java/org/restheart/utils/URLUtilisTest.java @@ -98,7 +98,7 @@ public void testGetUriWithDocId() { public void testGetUriWithDocIdStringValidObjectId() { System.out.println("getUriWithDocId String"); RequestContext context = prepareRequestContext(); - String expResult = "/dbName/collName/54d13711c2e692941728e1d3?doc_id_type=STRING"; + String expResult = "/dbName/collName/54d13711c2e692941728e1d3?id_type=STRING"; String result; try { result = URLUtils.getUriWithDocId(context, "dbName", "collName", "54d13711c2e692941728e1d3", false); @@ -112,7 +112,7 @@ public void testGetUriWithDocIdStringValidObjectId() { public void testGetUriWithLongDocId() { System.out.println("getUriWithDocId Integer"); RequestContext context = prepareRequestContext(); - String expResult = "/dbName/collName/123?doc_id_type=INT"; + String expResult = "/dbName/collName/123?id_type=NUMBER"; String result; try { result = URLUtils.getUriWithDocId(context, "dbName", "collName", 123, false); From 524a8d65e4c4231ba90d298fd231a49d8f8f3558 Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Sat, 14 Feb 2015 19:10:33 +0100 Subject: [PATCH 15/17] files buckets representation in get /db are added in embedded array rh:buckets with FILES_BUCKET type --- .../CollectionRepresentationFactory.java | 1 - .../database/DBRepresentationFactory.java | 39 +++++++++++-------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java b/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java index 8bc4418af8..34f9f19754 100644 --- a/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java +++ b/src/main/java/org/restheart/handlers/collection/CollectionRepresentationFactory.java @@ -112,7 +112,6 @@ private void embeddedDocuments(List embeddedData, String requestPath, nrep.addProperty("_type", RequestContext.TYPE.DOCUMENT.name()); rep.addRepresentation("rh:doc", nrep); } - } } } diff --git a/src/main/java/org/restheart/handlers/database/DBRepresentationFactory.java b/src/main/java/org/restheart/handlers/database/DBRepresentationFactory.java index 3d321aeea0..2a34dfd58c 100644 --- a/src/main/java/org/restheart/handlers/database/DBRepresentationFactory.java +++ b/src/main/java/org/restheart/handlers/database/DBRepresentationFactory.java @@ -37,7 +37,7 @@ */ public class DBRepresentationFactory extends AbstractRepresentationFactory { - private static final Logger logger = LoggerFactory.getLogger(DBRepresentationFactory.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DBRepresentationFactory.class); public DBRepresentationFactory() { } @@ -56,17 +56,17 @@ protected Representation getRepresentation(HttpServerExchange exchange, RequestC addSizeAndTotalPagesProperties(size, context, representation); addEmbeddedData(embeddedData, representation, requestPath); - + addPaginationLinks(exchange, context, size, representation); - + addLinkTemplatesAndCuries(exchange, context, representation, requestPath); - + return representation; } private void addEmbeddedData( - final List embeddedData, - final Representation rep, + final List embeddedData, + final Representation rep, final String requestPath) { if (embeddedData != null) { addReturnedProperty(embeddedData, rep); @@ -77,9 +77,9 @@ private void addEmbeddedData( } private void addLinkTemplatesAndCuries( - final HttpServerExchange exchange, - final RequestContext context, - final Representation rep, + final HttpServerExchange exchange, + final RequestContext context, + final Representation rep, final String requestPath) { // link templates and curies if (context.isParentAccessible()) { @@ -91,22 +91,27 @@ private void addLinkTemplatesAndCuries( } private void embeddedCollections( - final List embeddedData, - final String requestPath, + final List embeddedData, + final String requestPath, final Representation rep) { embeddedData.stream().forEach((d) -> { Object _id = d.get("_id"); - if (_id != null && (_id instanceof String || _id instanceof ObjectId)) { - Representation nrep = new Representation(requestPath + "/" + _id.toString()); - - nrep.addProperty("_type", RequestContext.TYPE.COLLECTION.name()); + if (_id != null && _id instanceof String) { + String id = (String) _id; + Representation nrep = new Representation(requestPath + "/" + id); nrep.addProperties(d); - rep.addRepresentation("rh:coll", nrep); + if (id.endsWith(RequestContext.FS_FILES_SUFFIX)) { + nrep.addProperty("_type", RequestContext.TYPE.FILES_BUCKET.name()); + rep.addRepresentation("rh:bucket", nrep); + } else { + nrep.addProperty("_type", RequestContext.TYPE.COLLECTION.name()); + rep.addRepresentation("rh:coll", nrep); + } } else { - logger.error("document missing string _id field", d); + LOGGER.error("collection missing string _id field", d); } }); } From 96d9e6153f997954e8ac0586faf67670314c80be Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Sat, 14 Feb 2015 19:33:15 +0100 Subject: [PATCH 16/17] contentType added in GET /db/bucket.files/file/binary response if set in properties code refactoring better check of supported Media Type --- .../handlers/files/GetFileBinaryHandler.java | 7 +++- .../handlers/files/PostFileHandler.java | 20 ++-------- .../injectors/BodyInjectorHandler.java | 38 +++++++++++++++---- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/restheart/handlers/files/GetFileBinaryHandler.java b/src/main/java/org/restheart/handlers/files/GetFileBinaryHandler.java index 84487cab0c..dddb2f942e 100644 --- a/src/main/java/org/restheart/handlers/files/GetFileBinaryHandler.java +++ b/src/main/java/org/restheart/handlers/files/GetFileBinaryHandler.java @@ -95,7 +95,12 @@ private void sendBinaryContent(final GridFSDBFile dbsfile, final HttpServerExcha LOGGER.debug("Filename = {}", dbsfile.getFilename()); LOGGER.debug("Content length = {}", dbsfile.getLength()); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, APPLICATION_OCTET_STREAM); + if (dbsfile.get("contentType") != null) { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, dbsfile.get("contentType").toString()); + } else { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, APPLICATION_OCTET_STREAM); + } + exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, dbsfile.getLength()); exchange.getResponseHeaders().put(Headers.CONTENT_DISPOSITION, String.format("inline; filename=\"%s\"", extractFilename(dbsfile))); diff --git a/src/main/java/org/restheart/handlers/files/PostFileHandler.java b/src/main/java/org/restheart/handlers/files/PostFileHandler.java index 07aa54ac2e..28425206b3 100644 --- a/src/main/java/org/restheart/handlers/files/PostFileHandler.java +++ b/src/main/java/org/restheart/handlers/files/PostFileHandler.java @@ -26,15 +26,12 @@ import io.undertow.server.handlers.form.FormParserFactory; import io.undertow.server.handlers.form.FormData; import io.undertow.server.handlers.form.FormDataParser; -import io.undertow.util.HeaderValues; -import io.undertow.util.Headers; import io.undertow.util.HttpString; import java.io.IOException; import org.bson.types.ObjectId; import org.restheart.db.Database; import org.restheart.db.GridFsDAO; import org.restheart.db.GridFsRepository; -import org.restheart.hal.Representation; import org.restheart.handlers.PipedHttpHandler; import org.restheart.handlers.RequestContext; import org.restheart.utils.HttpStatus; @@ -69,17 +66,6 @@ public PostFileHandler(PipedHttpHandler next, Database dbsDAO) { @Override public void handleRequest(HttpServerExchange exchange, RequestContext context) throws Exception { - HeaderValues contentTypes = exchange.getRequestHeaders().get(Headers.CONTENT_TYPE); - - if (contentTypes.isEmpty() || contentTypes.stream().noneMatch(ct - -> ct.startsWith(Representation.APP_FORM_URLENCODED_TYPE) - || ct.startsWith(Representation.MULTIPART_FORM_DATA_TYPE))) { - - String errMsg = "Content-Type must be either: application/x-www-form-urlencoded or multipart/form-data"; - ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, errMsg); - return; - } - FormDataParser parser = this.formParserFactory.createParser(exchange); if (parser == null) { @@ -200,9 +186,9 @@ private String findFile(final FormData data) { private DBObject findProps(final FormData data) throws JSONParseException { DBObject result = new BasicDBObject(); if (data.getFirst("properties") != null) { - String metadataString = data.getFirst("properties").getValue(); - if (metadataString != null) { - result = (DBObject) JSON.parse(metadataString); + String propsString = data.getFirst("properties").getValue(); + if (propsString != null) { + result = (DBObject) JSON.parse(propsString); } } diff --git a/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java b/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java index f607b84e37..3db6aa11e8 100644 --- a/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java +++ b/src/main/java/org/restheart/handlers/injectors/BodyInjectorHandler.java @@ -39,9 +39,11 @@ public class BodyInjectorHandler extends PipedHttpHandler { private static final String ERROR_INVALID_CONTENTTYPE = "Content-Type must be either: " + Representation.HAL_JSON_MEDIA_TYPE - + ", " + Representation.JSON_MEDIA_TYPE - + ", " + Representation.APP_FORM_URLENCODED_TYPE - + ", " + Representation.MULTIPART_FORM_DATA_TYPE; + + " or " + Representation.JSON_MEDIA_TYPE; + + private static final String ERROR_INVALID_CONTENTTYPE_FILE = "Content-Type must be either: " + + Representation.APP_FORM_URLENCODED_TYPE + + " or " + Representation.MULTIPART_FORM_DATA_TYPE; /** * Creates a new instance of BodyInjectorHandler @@ -70,9 +72,17 @@ public void handleRequest(HttpServerExchange exchange, RequestContext context) t // check the content type HeaderValues contentTypes = exchange.getRequestHeaders().get(Headers.CONTENT_TYPE); - if (unsupportedContentType(contentTypes)) { - ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, ERROR_INVALID_CONTENTTYPE); - return; + if ((context.getType() == RequestContext.TYPE.FILE && context.getMethod() == RequestContext.METHOD.PUT) || + (context.getType() == RequestContext.TYPE.FILES_BUCKET && context.getMethod() == RequestContext.METHOD.POST)) { + if (unsupportedContentTypeFiles(contentTypes)) { + ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, ERROR_INVALID_CONTENTTYPE_FILE); + return; + } + } else { + if (unsupportedContentType(contentTypes)) { + ResponseHelper.endExchangeWithMessage(exchange, HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE, ERROR_INVALID_CONTENTTYPE); + return; + } } if (isNotFormData(contentTypes)) { @@ -150,8 +160,20 @@ private static boolean unsupportedContentType(HeaderValues contentTypes) { || contentTypes.isEmpty() || contentTypes.stream().noneMatch( ct -> ct.startsWith(Representation.HAL_JSON_MEDIA_TYPE) - || ct.startsWith(Representation.JSON_MEDIA_TYPE) - || ct.startsWith(Representation.APP_FORM_URLENCODED_TYPE) + || ct.startsWith(Representation.JSON_MEDIA_TYPE)); + } + + /** + * true is the content-type is unsupported + * + * @param contentTypes + * @return + */ + private static boolean unsupportedContentTypeFiles(HeaderValues contentTypes) { + return contentTypes == null + || contentTypes.isEmpty() + || contentTypes.stream().noneMatch( + ct -> ct.startsWith(Representation.APP_FORM_URLENCODED_TYPE) || ct.startsWith(Representation.MULTIPART_FORM_DATA_TYPE)); } } From a8a75cbd484c0eb2dab75073ecc6f175b9f1bf5c Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Sat, 14 Feb 2015 20:05:54 +0100 Subject: [PATCH 17/17] version set to 0.10.0 --- pom.xml | 2 +- src/main/java/org/restheart/Configuration.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 5d8609a139..8dce1e57b3 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.restheart restheart - 0.10.0-SNAPSHOT + 0.10.0 jar Restheart diff --git a/src/main/java/org/restheart/Configuration.java b/src/main/java/org/restheart/Configuration.java index 9151dcc3f1..2df8549041 100644 --- a/src/main/java/org/restheart/Configuration.java +++ b/src/main/java/org/restheart/Configuration.java @@ -49,7 +49,7 @@ public class Configuration { /** * URL pointing to the online documentation specific for this version. */ - public static final String RESTHEART_ONLINE_DOC_URL = "http://www.restheart.org/docs/v0.9"; + public static final String RESTHEART_ONLINE_DOC_URL = "http://www.restheart.org/docs/v0.10"; private static final Logger LOGGER = LoggerFactory.getLogger(Configuration.class);