From 072dd7b280b52cdb9093024c9d224956aa15638c Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 9 May 2015 21:51:06 +0200 Subject: [PATCH 1/8] Android: handle read-only documents, use ACLs for sharing --- .../docs/activity/DocumentViewActivity.java | 19 +++++++++++- .../docs/adapter/ShareListAdapter.java | 31 +++++++++++++------ .../sismics/docs/event/ShareSendEvent.java | 14 ++++----- .../docs/fragment/DocShareFragment.java | 6 ++-- 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java b/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java index 6b982c6d..00abd2b0 100644 --- a/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java +++ b/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java @@ -184,9 +184,26 @@ public class DocumentViewActivity extends AppCompatActivity { } @Override - public boolean onCreateOptionsMenu(Menu menu) { + public boolean onCreateOptionsMenu(final Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.document_view_activity, menu); + + // Silently get the document to know if it is writable by the current user + // If this call fails or is slow and the document is read-only, + // write actions will be allowed and will fail + DocumentResource.get(this, document.optString("id"), new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + boolean writable = response.optBoolean("writable"); + + menu.findItem(R.id.share).setVisible(writable); + menu.findItem(R.id.upload_file).setVisible(writable); + menu.findItem(R.id.edit).setVisible(writable); + menu.findItem(R.id.delete_file).setVisible(writable); + menu.findItem(R.id.delete_document).setVisible(writable); + } + }); + return super.onCreateOptionsMenu(menu); } diff --git a/docs-android/app/src/main/java/com/sismics/docs/adapter/ShareListAdapter.java b/docs-android/app/src/main/java/com/sismics/docs/adapter/ShareListAdapter.java index d0fffc76..6469866f 100644 --- a/docs-android/app/src/main/java/com/sismics/docs/adapter/ShareListAdapter.java +++ b/docs-android/app/src/main/java/com/sismics/docs/adapter/ShareListAdapter.java @@ -15,6 +15,9 @@ import com.sismics.docs.event.ShareSendEvent; import org.json.JSONArray; import org.json.JSONObject; +import java.util.ArrayList; +import java.util.List; + import de.greenrobot.event.EventBus; /** @@ -26,25 +29,33 @@ public class ShareListAdapter extends BaseAdapter { /** * Shares. */ - private JSONArray shares; + private List acls; /** * Share list adapter. * - * @param shares Shares + * @param acls ACLs */ - public ShareListAdapter(JSONArray shares) { - this.shares = shares; + public ShareListAdapter(JSONArray acls) { + this.acls = new ArrayList<>(); + + // Extract only share ACLs + for (int i = 0; i < acls.length(); i++) { + JSONObject acl = acls.optJSONObject(i); + if (acl.optString("type").equals("SHARE")) { + this.acls.add(acl); + } + } } @Override public int getCount() { - return shares.length(); + return acls.size(); } @Override public JSONObject getItem(int position) { - return shares.optJSONObject(position); + return acls.get(position); } @Override @@ -60,8 +71,8 @@ public class ShareListAdapter extends BaseAdapter { } // Fill the view - final JSONObject share = getItem(position); - String name = share.optString("name"); + final JSONObject acl = getItem(position); + String name = acl.optString("name"); TextView shareTextView = (TextView) view.findViewById(R.id.shareTextView); shareTextView.setText(name.isEmpty() ? parent.getContext().getString(R.string.share_default_name) : name); @@ -70,7 +81,7 @@ public class ShareListAdapter extends BaseAdapter { shareDeleteButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - EventBus.getDefault().post(new ShareDeleteEvent(share.optString("id"))); + EventBus.getDefault().post(new ShareDeleteEvent(acl.optString("id"))); } }); @@ -79,7 +90,7 @@ public class ShareListAdapter extends BaseAdapter { shareSendButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - EventBus.getDefault().post(new ShareSendEvent(share)); + EventBus.getDefault().post(new ShareSendEvent(acl)); } }); diff --git a/docs-android/app/src/main/java/com/sismics/docs/event/ShareSendEvent.java b/docs-android/app/src/main/java/com/sismics/docs/event/ShareSendEvent.java index 8e48e1ca..9d922648 100644 --- a/docs-android/app/src/main/java/com/sismics/docs/event/ShareSendEvent.java +++ b/docs-android/app/src/main/java/com/sismics/docs/event/ShareSendEvent.java @@ -9,20 +9,20 @@ import org.json.JSONObject; */ public class ShareSendEvent { /** - * Share data. + * ACL data. */ - private JSONObject share; + private JSONObject acl; /** * Create a share send event. * - * @param share Share data + * @param acl ACL data */ - public ShareSendEvent(JSONObject share) { - this.share = share; + public ShareSendEvent(JSONObject acl) { + this.acl = acl; } - public JSONObject getShare() { - return share; + public JSONObject getAcl() { + return acl; } } diff --git a/docs-android/app/src/main/java/com/sismics/docs/fragment/DocShareFragment.java b/docs-android/app/src/main/java/com/sismics/docs/fragment/DocShareFragment.java index a22ccc0d..93cb93d2 100644 --- a/docs-android/app/src/main/java/com/sismics/docs/fragment/DocShareFragment.java +++ b/docs-android/app/src/main/java/com/sismics/docs/fragment/DocShareFragment.java @@ -125,10 +125,10 @@ public class DocShareFragment extends DialogFragment { @Override public void onSuccess(int statusCode, Header[] headers, JSONObject response) { document = response; - JSONArray shares = response.optJSONArray("shares"); + JSONArray acls = response.optJSONArray("acls"); shareProgressBar.setVisibility(View.GONE); shareListView.setEmptyView(shareEmptyView); - shareListView.setAdapter(new ShareListAdapter(shares)); + shareListView.setAdapter(new ShareListAdapter(acls)); } @Override @@ -158,7 +158,7 @@ public class DocShareFragment extends DialogFragment { // Build the share link String serverUrl = PreferenceUtil.getServerUrl(getActivity()); - String link = serverUrl + "/share.html#/share/" + document.optString("id") + "/" + event.getShare().optString("id"); + String link = serverUrl + "/share.html#/share/" + document.optString("id") + "/" + event.getAcl().optString("id"); // Build the intent Context context = getActivity(); From 52387d93acb0821df329fc241993ef211e606b3f Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 9 May 2015 21:52:01 +0200 Subject: [PATCH 2/8] Closes #13: Don't show tags from other users --- .../com/sismics/docs/core/dao/jpa/TagDao.java | 4 ++- .../docs/rest/resource/DocumentResource.java | 36 +++++++++++-------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java index b8d5b2fd..c2a66103 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/TagDao.java @@ -75,16 +75,18 @@ public class TagDao { * @return */ @SuppressWarnings("unchecked") - public List getByDocumentId(String documentId) { + public List getByDocumentId(String documentId, String userId) { EntityManager em = ThreadLocalContext.get().getEntityManager(); StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C from T_DOCUMENT_TAG dt "); sb.append(" join T_TAG t on t.TAG_ID_C = dt.DOT_IDTAG_C "); sb.append(" where dt.DOT_IDDOCUMENT_C = :documentId and t.TAG_DELETEDATE_D is null "); + sb.append(" and t.TAG_IDUSER_C = :userId "); sb.append(" order by t.TAG_NAME_C "); // Perform the query Query q = em.createNativeQuery(sb.toString()); q.setParameter("documentId", documentId); + q.setParameter("userId", userId); List l = q.getResultList(); // Assemble results diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index 61c3cc06..3e876c99 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -105,18 +105,23 @@ public class DocumentResource extends BaseResource { document.put("language", documentDb.getLanguage()); document.put("creator", userDao.getById(documentDb.getUserId()).getUsername()); - // Add tags - TagDao tagDao = new TagDao(); - List tagDtoList = tagDao.getByDocumentId(documentId); - List tags = new ArrayList<>(); - for (TagDto tagDto : tagDtoList) { - JSONObject tag = new JSONObject(); - tag.put("id", tagDto.getId()); - tag.put("name", tagDto.getName()); - tag.put("color", tagDto.getColor()); - tags.add(tag); + if (principal.isAnonymous()) { + // No tags in anonymous mode (sharing) + document.put("tags", new ArrayList()); + } else { + // Add tags added by the current user on this document + TagDao tagDao = new TagDao(); + List tagDtoList = tagDao.getByDocumentId(documentId, principal.getId()); + List tags = new ArrayList<>(); + for (TagDto tagDto : tagDtoList) { + JSONObject tag = new JSONObject(); + tag.put("id", tagDto.getId()); + tag.put("name", tagDto.getName()); + tag.put("color", tagDto.getColor()); + tags.add(tag); + } + document.put("tags", tags); } - document.put("tags", tags); // Add ACL List aclDtoList = aclDao.getBySourceId(documentId); @@ -130,7 +135,10 @@ public class DocumentResource extends BaseResource { acl.put("type", aclDto.getTargetType()); aclList.add(acl); - if (aclDto.getTargetId().equals(principal.getId()) && aclDto.getPerm() == PermType.WRITE) { + if (!principal.isAnonymous() + && aclDto.getTargetId().equals(principal.getId()) + && aclDto.getPerm() == PermType.WRITE) { + // The document is writable for the current user writable = true; } } @@ -186,8 +194,8 @@ public class DocumentResource extends BaseResource { document.put("language", documentDto.getLanguage()); document.put("file_count", documentDto.getFileCount()); - // Get tags - List tagDtoList = tagDao.getByDocumentId(documentDto.getId()); + // Get tags added by the current user on this document + List tagDtoList = tagDao.getByDocumentId(documentDto.getId(), principal.getId()); List tags = new ArrayList<>(); for (TagDto tagDto : tagDtoList) { JSONObject tag = new JSONObject(); From c727ac3a56f0a47e0ed072ea78f25979b85b5382 Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 9 May 2015 23:34:43 +0200 Subject: [PATCH 3/8] Refactor, TODO --- .../com/sismics/docs/rest/resource/DocumentResource.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index 3e876c99..af836c23 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -103,7 +103,7 @@ public class DocumentResource extends BaseResource { document.put("description", documentDb.getDescription()); document.put("create_date", documentDb.getCreateDate().getTime()); document.put("language", documentDb.getLanguage()); - document.put("creator", userDao.getById(documentDb.getUserId()).getUsername()); + // TODO Add "shared" and "file_count" -> rewrite the query in SQL if (principal.isAnonymous()) { // No tags in anonymous mode (sharing) @@ -123,6 +123,10 @@ public class DocumentResource extends BaseResource { document.put("tags", tags); } + // Below is specific to GET /document/id + + document.put("creator", userDao.getById(documentDb.getUserId()).getUsername()); + // Add ACL List aclDtoList = aclDao.getBySourceId(documentId); List aclList = new ArrayList<>(); @@ -190,8 +194,8 @@ public class DocumentResource extends BaseResource { document.put("title", documentDto.getTitle()); document.put("description", documentDto.getDescription()); document.put("create_date", documentDto.getCreateTimestamp()); - document.put("shared", documentDto.getShared()); document.put("language", documentDto.getLanguage()); + document.put("shared", documentDto.getShared()); document.put("file_count", documentDto.getFileCount()); // Get tags added by the current user on this document From 25136bc1468f201634d99e142acd99b7665196ef Mon Sep 17 00:00:00 2001 From: jendib Date: Sat, 9 May 2015 23:34:43 +0200 Subject: [PATCH 4/8] Refactor, TODO --- .../docs/activity/DocumentViewActivity.java | 59 ++++++++++++------- .../docs/rest/resource/DocumentResource.java | 8 ++- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java b/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java index 00abd2b0..f927e9ae 100644 --- a/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java +++ b/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentViewActivity.java @@ -71,17 +71,22 @@ public class DocumentViewActivity extends AppCompatActivity { /** * File view pager. */ - ViewPager fileViewPager; + private ViewPager fileViewPager; /** * File pager adapter. */ - FilePagerAdapter filePagerAdapter; + private FilePagerAdapter filePagerAdapter; /** * Document displayed. */ - JSONObject document; + private JSONObject document; + + /** + * Menu. + */ + private Menu menu; @Override protected void onCreate(final Bundle args) { @@ -181,29 +186,16 @@ public class DocumentViewActivity extends AppCompatActivity { // Grab the attached files updateFiles(); + + // Grab the full document (used for ACLs and writable status) + updateDocument(); } @Override public boolean onCreateOptionsMenu(final Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.document_view_activity, menu); - - // Silently get the document to know if it is writable by the current user - // If this call fails or is slow and the document is read-only, - // write actions will be allowed and will fail - DocumentResource.get(this, document.optString("id"), new JsonHttpResponseHandler() { - @Override - public void onSuccess(int statusCode, Header[] headers, JSONObject response) { - boolean writable = response.optBoolean("writable"); - - menu.findItem(R.id.share).setVisible(writable); - menu.findItem(R.id.upload_file).setVisible(writable); - menu.findItem(R.id.edit).setVisible(writable); - menu.findItem(R.id.delete_file).setVisible(writable); - menu.findItem(R.id.delete_document).setVisible(writable); - } - }); - + this.menu = menu; return super.onCreateOptionsMenu(menu); } @@ -510,6 +502,33 @@ public class DocumentViewActivity extends AppCompatActivity { } } + /** + * Update the document model. + */ + private void updateDocument() { + if (document == null) return; + + // Silently get the document to know if it is writable by the current user + // If this call fails or is slow and the document is read-only, + // write actions will be allowed and will fail + DocumentResource.get(this, document.optString("id"), new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + boolean writable = response.optBoolean("writable"); + + if (menu != null) { + menu.findItem(R.id.share).setVisible(writable); + menu.findItem(R.id.upload_file).setVisible(writable); + menu.findItem(R.id.edit).setVisible(writable); + menu.findItem(R.id.delete_file).setVisible(writable); + menu.findItem(R.id.delete_document).setVisible(writable); + } + + // TODO Show the ACLs in a sliding panel from the right + } + }); + } + /** * Refresh files list. */ diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index 3e876c99..af836c23 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -103,7 +103,7 @@ public class DocumentResource extends BaseResource { document.put("description", documentDb.getDescription()); document.put("create_date", documentDb.getCreateDate().getTime()); document.put("language", documentDb.getLanguage()); - document.put("creator", userDao.getById(documentDb.getUserId()).getUsername()); + // TODO Add "shared" and "file_count" -> rewrite the query in SQL if (principal.isAnonymous()) { // No tags in anonymous mode (sharing) @@ -123,6 +123,10 @@ public class DocumentResource extends BaseResource { document.put("tags", tags); } + // Below is specific to GET /document/id + + document.put("creator", userDao.getById(documentDb.getUserId()).getUsername()); + // Add ACL List aclDtoList = aclDao.getBySourceId(documentId); List aclList = new ArrayList<>(); @@ -190,8 +194,8 @@ public class DocumentResource extends BaseResource { document.put("title", documentDto.getTitle()); document.put("description", documentDto.getDescription()); document.put("create_date", documentDto.getCreateTimestamp()); - document.put("shared", documentDto.getShared()); document.put("language", documentDto.getLanguage()); + document.put("shared", documentDto.getShared()); document.put("file_count", documentDto.getFileCount()); // Get tags added by the current user on this document From 451d9134428eaa5b0c322153ce08f02bcf3161a5 Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 10 May 2015 13:12:23 +0200 Subject: [PATCH 5/8] Simplify adding READ+WRITE ACL --- .../src/app/docs/controller/DocumentView.js | 46 ++++++++++++++----- .../src/partial/docs/document.view.html | 4 +- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js b/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js index 18d50e78..3bd93077 100644 --- a/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js +++ b/docs-web/src/main/webapp/src/app/docs/controller/DocumentView.js @@ -151,7 +151,6 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta /** * File has been drag & dropped. - * @param files */ $scope.fileDropped = function(files) { if (!$scope.document.writable) { @@ -186,8 +185,6 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta /** * Upload a file. - * @param file - * @param newfile */ $scope.uploadFile = function(file, newfile) { // Upload the file @@ -210,7 +207,6 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta /** * Delete an ACL. - * @param acl */ $scope.deleteAcl = function(acl) { Restangular.one('acl/' + $stateParams.id + '/' + acl.perm + '/' + acl.id, null).remove().then(function () { @@ -224,17 +220,45 @@ angular.module('docs').controller('DocumentView', function ($scope, $state, $sta * Add an ACL. */ $scope.addAcl = function() { + // Compute ACLs to add $scope.acl.source = $stateParams.id; - Restangular.one('acl').put($scope.acl).then(function(acl) { - $scope.acl = { perm: 'READ' }; - if (_.isUndefined(acl.id)) { - return; - } - $scope.document.acls.push(acl); - $scope.document.acls = angular.copy($scope.document.acls); + var acls = []; + if ($scope.acl.perm == 'READWRITE') { + acls = [{ + source: $stateParams.id, + username: $scope.acl.username, + perm: 'READ' + }, { + source: $stateParams.id, + username: $scope.acl.username, + perm: 'WRITE' + }]; + } else { + acls = [{ + source: $stateParams.id, + username: $scope.acl.username, + perm: $scope.acl.perm + }]; + } + + // Add ACLs + _.each(acls, function(acl) { + Restangular.one('acl').put(acl).then(function(acl) { + if (_.isUndefined(acl.id)) { + return; + } + $scope.document.acls.push(acl); + $scope.document.acls = angular.copy($scope.document.acls); + }); }); + + // Reset form + $scope.acl = { perm: 'READ' }; }; + /** + * Auto-complete on ACL target. + */ $scope.getTargetAclTypeahead = function($viewValue) { var deferred = $q.defer(); Restangular.one('acl/target/search') diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.html b/docs-web/src/main/webapp/src/partial/docs/document.view.html index 691e909a..7975ec1b 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.view.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.view.html @@ -118,8 +118,8 @@
From 1e3282eff30e6c39d230167e3f6f67d40d16e02d Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 10 May 2015 13:19:16 +0200 Subject: [PATCH 6/8] Fix non-deletable ACL --- docs-web/src/main/webapp/src/partial/docs/document.view.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.html b/docs-web/src/main/webapp/src/partial/docs/document.view.html index 7975ec1b..1560225c 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.view.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.view.html @@ -92,7 +92,7 @@ {{ a.perm }} - From b7f920f864591a30d23a7a8abdbbd28d20326081 Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 10 May 2015 13:45:39 +0200 Subject: [PATCH 7/8] Native query for GET /document/id --- .../docs/core/dao/jpa/DocumentDao.java | 24 ++++++++++++++++--- .../docs/core/dao/jpa/dto/DocumentDto.java | 21 ++++++++++++++++ .../docs/rest/resource/DocumentResource.java | 21 ++++++++-------- .../docs/rest/resource/FileResource.java | 7 +++--- .../docs/rest/TestDocumentResource.java | 15 ++++++++---- 5 files changed, 67 insertions(+), 21 deletions(-) diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java index b6c1bb14..28e509b3 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/DocumentDao.java @@ -68,11 +68,29 @@ public class DocumentDao { * @param id Document ID * @return Document */ - public Document getDocument(String id) { + public DocumentDto getDocument(String id) { EntityManager em = ThreadLocalContext.get().getEntityManager(); - Query q = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null"); + StringBuilder sb = new StringBuilder("select d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_CREATEDATE_D, d.DOC_LANGUAGE_C, "); + sb.append(" (select count(s.SHA_ID_C) from T_SHARE s, T_ACL ac where ac.ACL_SOURCEID_C = d.DOC_ID_C and ac.ACL_TARGETID_C = s.SHA_ID_C and ac.ACL_DELETEDATE_D is null and s.SHA_DELETEDATE_D is null), "); + sb.append(" (select count(f.FIL_ID_C) from T_FILE f where f.FIL_DELETEDATE_D is null and f.FIL_IDDOC_C = d.DOC_ID_C), "); + sb.append(" u.USE_USERNAME_C "); + sb.append(" from T_DOCUMENT d, T_USER u "); + sb.append(" where d.DOC_IDUSER_C = u.USE_ID_C and d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null "); + Query q = em.createNativeQuery(sb.toString()); q.setParameter("id", id); - return (Document) q.getSingleResult(); + Object[] o = (Object[]) q.getSingleResult(); + + DocumentDto documentDto = new DocumentDto(); + int i = 0; + documentDto.setId((String) o[i++]); + documentDto.setTitle((String) o[i++]); + documentDto.setDescription((String) o[i++]); + documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime()); + documentDto.setLanguage((String) o[i++]); + documentDto.setShared(((Number) o[i++]).intValue() > 0); + documentDto.setFileCount(((Number) o[i++]).intValue()); + documentDto.setCreator((String) o[i++]); + return documentDto; } /** diff --git a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/DocumentDto.java b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/DocumentDto.java index fe7eedf7..38414c80 100644 --- a/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/DocumentDto.java +++ b/docs-core/src/main/java/com/sismics/docs/core/dao/jpa/dto/DocumentDto.java @@ -44,6 +44,11 @@ public class DocumentDto { */ private Integer fileCount; + /** + * Document creator. + */ + private String creator; + /** * Getter de id. * @@ -167,4 +172,20 @@ public class DocumentDto { public void setFileCount(Integer fileCount) { this.fileCount = fileCount; } + + /** + * Getter of creator. + * @return creator + */ + public String getCreator() { + return creator; + } + + /** + * Setter of creator. + * @param creator creator + */ + public void setCreator(String creator) { + this.creator = creator; + } } diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java index af836c23..8e39e17c 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/DocumentResource.java @@ -38,7 +38,6 @@ import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.DocumentDao; import com.sismics.docs.core.dao.jpa.FileDao; import com.sismics.docs.core.dao.jpa.TagDao; -import com.sismics.docs.core.dao.jpa.UserDao; import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria; import com.sismics.docs.core.dao.jpa.dto.AclDto; import com.sismics.docs.core.dao.jpa.dto.DocumentDto; @@ -84,10 +83,9 @@ public class DocumentResource extends BaseResource { DocumentDao documentDao = new DocumentDao(); AclDao aclDao = new AclDao(); - UserDao userDao = new UserDao(); - Document documentDb; + DocumentDto documentDto; try { - documentDb = documentDao.getDocument(documentId); + documentDto = documentDao.getDocument(documentId); // Check document visibility if (!aclDao.checkPermission(documentId, PermType.READ, shareId == null ? principal.getId() : shareId)) { @@ -98,12 +96,13 @@ public class DocumentResource extends BaseResource { } JSONObject document = new JSONObject(); - document.put("id", documentDb.getId()); - document.put("title", documentDb.getTitle()); - document.put("description", documentDb.getDescription()); - document.put("create_date", documentDb.getCreateDate().getTime()); - document.put("language", documentDb.getLanguage()); - // TODO Add "shared" and "file_count" -> rewrite the query in SQL + document.put("id", documentDto.getId()); + document.put("title", documentDto.getTitle()); + document.put("description", documentDto.getDescription()); + document.put("create_date", documentDto.getCreateTimestamp()); + document.put("language", documentDto.getLanguage()); + document.put("shared", documentDto.getShared()); + document.put("file_count", documentDto.getFileCount()); if (principal.isAnonymous()) { // No tags in anonymous mode (sharing) @@ -125,7 +124,7 @@ public class DocumentResource extends BaseResource { // Below is specific to GET /document/id - document.put("creator", userDao.getById(documentDb.getUserId()).getUsername()); + document.put("creator", documentDto.getCreator()); // Add ACL List aclDtoList = aclDao.getBySourceId(documentId); diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java index 455b50e2..4f909015 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/FileResource.java @@ -41,6 +41,7 @@ import com.sismics.docs.core.dao.jpa.AclDao; import com.sismics.docs.core.dao.jpa.DocumentDao; import com.sismics.docs.core.dao.jpa.FileDao; import com.sismics.docs.core.dao.jpa.UserDao; +import com.sismics.docs.core.dao.jpa.dto.DocumentDto; import com.sismics.docs.core.event.FileCreatedAsyncEvent; import com.sismics.docs.core.event.FileDeletedAsyncEvent; import com.sismics.docs.core.model.context.AppContext; @@ -486,9 +487,9 @@ public class FileResource extends BaseResource { // Get the document DocumentDao documentDao = new DocumentDao(); - Document document; + DocumentDto documentDto; try { - document = documentDao.getDocument(documentId); + documentDto = documentDao.getDocument(documentId); // Check document visibility AclDao aclDao = new AclDao(); @@ -537,7 +538,7 @@ public class FileResource extends BaseResource { // Write to the output return Response.ok(stream) .header("Content-Type", "application/zip") - .header("Content-Disposition", "attachment; filename=\"" + document.getTitle().replaceAll("\\W+", "_") + ".zip\"") + .header("Content-Disposition", "attachment; filename=\"" + documentDto.getTitle().replaceAll("\\W+", "_") + ".zip\"") .build(); } } diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java index 24168ec9..88ebe3b1 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java @@ -138,8 +138,8 @@ public class TestDocumentResource extends BaseJerseyTest { postParams.add("title", "My super title document 1"); postParams.add("description", "My super description for document 1"); postParams.add("language", "eng"); - create1Date = new Date().getTime(); - postParams.add("create_date", create1Date); + long create3Date = new Date().getTime(); + postParams.add("create_date", create3Date); response = documentResource.put(ClientResponse.class, postParams); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); json = response.getEntity(JSONObject.class); @@ -148,14 +148,14 @@ public class TestDocumentResource extends BaseJerseyTest { // Add a file fileResource = resource().path("/file"); - fileResource.addFilter(new CookieAuthenticationFilter(document1Token)); + fileResource.addFilter(new CookieAuthenticationFilter(document3Token)); form = new FormDataMultiPart(); file = this.getClass().getResourceAsStream("/file/Einstein-Roosevelt-letter.png"); fdp = new FormDataBodyPart("file", new BufferedInputStream(file), MediaType.APPLICATION_OCTET_STREAM_TYPE); form.bodyPart(fdp); - form.field("id", document1Id); + form.field("id", document3Id); response = fileResource.type(MediaType.MULTIPART_FORM_DATA).put(ClientResponse.class, form); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); json = response.getEntity(JSONObject.class); @@ -206,6 +206,13 @@ public class TestDocumentResource extends BaseJerseyTest { json = response.getEntity(JSONObject.class); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); Assert.assertEquals(document1Id, json.getString("id")); + Assert.assertEquals("document1", json.getString("creator")); + Assert.assertEquals(1, json.getInt("file_count")); + Assert.assertEquals(true, json.getBoolean("shared")); + Assert.assertEquals("My super title document 1", json.getString("title")); + Assert.assertEquals("My super description for document 1", json.getString("description")); + Assert.assertEquals("eng", json.getString("language")); + Assert.assertEquals(create1Date, json.getLong("create_date")); tags = json.getJSONArray("tags"); Assert.assertEquals(1, tags.length()); Assert.assertEquals(tag1Id, tags.getJSONObject(0).getString("id")); From 8597eac9f97dccaae90d7fed12ede164dd8d8b2d Mon Sep 17 00:00:00 2001 From: jendib Date: Sun, 10 May 2015 13:55:49 +0200 Subject: [PATCH 8/8] Fix ACL resource --- .../java/com/sismics/docs/rest/resource/AclResource.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java b/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java index 448ca2b2..59bb904e 100644 --- a/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java +++ b/docs-web/src/main/java/com/sismics/docs/rest/resource/AclResource.java @@ -58,7 +58,7 @@ public class AclResource extends BaseResource { } // Validate input - sourceId = ValidationUtil.validateLength(sourceId, "source", 36, 36, false); + ValidationUtil.validateRequired(sourceId, "source"); PermType perm = PermType.valueOf(ValidationUtil.validateLength(permStr, "perm", 1, 30, false)); username = ValidationUtil.validateLength(username, "username", 1, 50, false); @@ -105,7 +105,7 @@ public class AclResource extends BaseResource { * @throws JSONException */ @DELETE - @Path("{sourceId: [a-z0-9\\-]+}/{perm: READ|WRITE}/{targetId: [a-z0-9\\-]+}") + @Path("{sourceId: [a-z0-9\\-]+}/{perm: [A-Z]+}/{targetId: [a-z0-9\\-]+}") @Produces(MediaType.APPLICATION_JSON) public Response delete( @PathParam("sourceId") String sourceId, @@ -116,9 +116,9 @@ public class AclResource extends BaseResource { } // Validate input - sourceId = ValidationUtil.validateLength(sourceId, "source", 36, 36, false); + ValidationUtil.validateRequired(sourceId, "source"); PermType perm = PermType.valueOf(ValidationUtil.validateLength(permStr, "perm", 1, 30, false)); - targetId = ValidationUtil.validateLength(targetId, "target", 36, 36, false); + ValidationUtil.validateRequired(targetId, "target"); // Check permission on the source by the principal AclDao aclDao = new AclDao();