Merge pull request #86 from sismics/master

Push to production
This commit is contained in:
Benjamin Gamard 2016-03-21 01:01:14 +01:00
commit 6aef7246a0
138 changed files with 3949 additions and 727 deletions

View File

@ -28,6 +28,7 @@ Features
- 256-bit AES encryption
- Tag system with relations
- Multi-users ACL system
- Hierarchical groups
- Audit log
- Comments
- Storage quota per user

View File

@ -77,10 +77,13 @@
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.1.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.shamanland/fab/0.0.6/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/animated-vector-drawable/23.2.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.2.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/23.2.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.2.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.2.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-vector-drawable/23.2.1/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.easing/android-easing/1.0.3/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/it.sephiroth.android.library.imagezoom/imagezoom/1.0.5/jars" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
@ -92,19 +95,21 @@
</content>
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="support-annotations-23.1.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.2.1" level="project" />
<orderEntry type="library" exported="" name="android-easing-1.0.3" level="project" />
<orderEntry type="library" exported="" name="imagezoom-1.0.5" level="project" />
<orderEntry type="library" exported="" name="design-23.2.1" level="project" />
<orderEntry type="library" exported="" name="okhttp-urlconnection-3.1.1" level="project" />
<orderEntry type="library" exported="" name="picasso-2.5.2" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-23.1.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-23.1.1" level="project" />
<orderEntry type="library" exported="" name="fab-0.0.6" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.1.1" level="project" />
<orderEntry type="library" exported="" name="animated-vector-drawable-23.2.1" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-23.2.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.2.1" level="project" />
<orderEntry type="library" exported="" name="okhttp-3.1.1" level="project" />
<orderEntry type="library" exported="" name="okio-1.6.0" level="project" />
<orderEntry type="library" exported="" name="support-vector-drawable-23.2.1" level="project" />
<orderEntry type="library" exported="" name="picasso2-okhttp3-downloader-1.0.2" level="project" />
<orderEntry type="library" exported="" name="tokenautocomplete-1.2.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-23.2.1" level="project" />
<orderEntry type="library" exported="" name="eventbus-3.0.0" level="project" />
</component>
</module>

View File

@ -3,7 +3,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.0.0-beta5'
classpath 'com.android.tools.build:gradle:2.1.0-alpha3'
}
}
apply plugin: 'com.android.application'
@ -50,11 +50,11 @@ android {
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:recyclerview-v7:23.1.1'
compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support:recyclerview-v7:23.2.1'
compile 'com.android.support:design:23.2.1'
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
compile 'org.greenrobot:eventbus:3.0.0'
compile 'com.shamanland:fab:0.0.6'
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.1.1'
compile "com.squareup.okhttp3:okhttp-urlconnection:3.1.1"

View File

@ -47,6 +47,16 @@
android:name=".activity.DocumentEditActivity"
android:label="@string/new_document">
</activity>
<activity
android:name=".activity.AuditLogActivity"
android:label="@string/latest_activity">
</activity>
<activity
android:name=".activity.UserProfileActivity">
</activity>
<activity
android:name=".activity.GroupProfileActivity">
</activity>
<activity
android:name=".activity.SettingsActivity"
android:label="@string/settings">

View File

@ -0,0 +1,121 @@
package com.sismics.docs.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.ProgressBar;
import com.sismics.docs.R;
import com.sismics.docs.adapter.AuditLogListAdapter;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.resource.AuditLogResource;
import org.json.JSONObject;
/**
* Audit log activity.
*
* @author bgamard.
*/
public class AuditLogActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check if logged in
if (!ApplicationContext.getInstance().isLoggedIn()) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
// Handle activity context
if (getIntent() == null) {
finish();
return;
}
// Input document ID (optional)
final String documentId = getIntent().getStringExtra("documentId");
// Setup the activity
setContentView(R.layout.auditlog_activity);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
// Configure the swipe refresh layout
SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
refreshView(documentId);
}
});
// Navigate to user profile on click
final ListView auditLogListView = (ListView) findViewById(R.id.auditLogListView);
auditLogListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (auditLogListView.getAdapter() == null) {
return;
}
AuditLogListAdapter adapter = (AuditLogListAdapter) auditLogListView.getAdapter();
String username = adapter.getItem(position).optString("username");
Intent intent = new Intent(AuditLogActivity.this, UserProfileActivity.class);
intent.putExtra("username", username);
startActivity(intent);
}
});
// Get audit log list
refreshView(documentId);
}
/**
* Refresh the view.
*/
private void refreshView(String documentId) {
final SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
final ListView auditLogListView = (ListView) findViewById(R.id.auditLogListView);
progressBar.setVisibility(View.VISIBLE);
auditLogListView.setVisibility(View.GONE);
AuditLogResource.list(this, documentId, new HttpCallback() {
@Override
public void onSuccess(JSONObject response) {
auditLogListView.setAdapter(new AuditLogListAdapter(response.optJSONArray("logs")));
}
@Override
public void onFinish() {
progressBar.setVisibility(View.GONE);
auditLogListView.setVisibility(View.VISIBLE);
swipeRefreshLayout.setRefreshing(false);
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -154,7 +154,7 @@ public class DocumentViewActivity extends AppCompatActivity {
*
* @param document Document in JSON format
*/
private void refreshDocument(JSONObject document) {
private void refreshDocument(final JSONObject document) {
this.document = document;
String title = document.optString("title");
@ -249,7 +249,7 @@ public class DocumentViewActivity extends AppCompatActivity {
@Override
public void onClick(View view) {
DialogFragment dialog = DocExportPdfFragment.newInstance(
DocumentViewActivity.this.document.optString("id"), DocumentViewActivity.this.document.optString("title"));
document.optString("id"), document.optString("title"));
dialog.show(getSupportFragmentManager(), "DocExportPdfFragment");
}
});
@ -259,11 +259,22 @@ public class DocumentViewActivity extends AppCompatActivity {
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DialogFragment dialog = DocShareFragment.newInstance(DocumentViewActivity.this.document.optString("id"));
DialogFragment dialog = DocShareFragment.newInstance(document.optString("id"));
dialog.show(getSupportFragmentManager(), "DocShareFragment");
}
});
// Action audit log
button = (Button) findViewById(R.id.actionAuditLog);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(DocumentViewActivity.this, AuditLogActivity.class);
intent.putExtra("documentId", document.optString("id"));
startActivity(intent);
}
});
// Button add a comment
ImageButton imageButton = (ImageButton) findViewById(R.id.addCommentBtn);
imageButton.setOnClickListener(new View.OnClickListener() {
@ -300,7 +311,7 @@ public class DocumentViewActivity extends AppCompatActivity {
// Grab the attached files
updateFiles();
// Grab the full document (used for ACLs and writable status)
// Grab the full document (used for ACLs, remaining metadata and writable status)
updateDocument();
}
@ -630,6 +641,7 @@ public class DocumentViewActivity extends AppCompatActivity {
menu.findItem(R.id.delete_file).setVisible(writable);
}
// Action only available if the document is writable
findViewById(R.id.actionEditDocument).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
findViewById(R.id.actionUploadFile).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
findViewById(R.id.actionSharing).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
@ -637,7 +649,36 @@ public class DocumentViewActivity extends AppCompatActivity {
// ACLs
ListView aclListView = (ListView) findViewById(R.id.aclListView);
aclListView.setAdapter(new AclListAdapter(document.optJSONArray("acls")));
final AclListAdapter aclListAdapter = new AclListAdapter(document.optJSONArray("acls"));
aclListView.setAdapter(aclListAdapter);
aclListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
AclListAdapter.AclItem acl = aclListAdapter.getItem(position);
if (acl.getType().equals("USER")) {
Intent intent = new Intent(DocumentViewActivity.this, UserProfileActivity.class);
intent.putExtra("username", acl.getName());
startActivity(intent);
} else if (acl.getType().equals("GROUP")) {
Intent intent = new Intent(DocumentViewActivity.this, GroupProfileActivity.class);
intent.putExtra("name", acl.getName());
startActivity(intent);
}
}
});
// Remaining metadata
TextView creatorTextView = (TextView) findViewById(R.id.creatorTextView);
final String creator = document.optString("creator");
creatorTextView.setText(creator);
creatorTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(DocumentViewActivity.this, UserProfileActivity.class);
intent.putExtra("username", creator);
startActivity(intent);
}
});
}
});
}

View File

@ -0,0 +1,92 @@
package com.sismics.docs.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.sismics.docs.R;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.resource.UserResource;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* Group profile activity.
*
* @author bgamard.
*/
public class GroupProfileActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check if logged in
if (!ApplicationContext.getInstance().isLoggedIn()) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
// Handle activity context
if (getIntent() == null) {
finish();
return;
}
// Input name
final String name = getIntent().getStringExtra("name");
if (name == null) {
finish();
return;
}
// Setup the activity
setTitle(name);
setContentView(R.layout.groupprofile_activity);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
// Get the group and populate the view
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
final View layoutView = findViewById(R.id.layout);
progressBar.setVisibility(View.VISIBLE);
layoutView.setVisibility(View.GONE);
UserResource.get(this, name, new HttpCallback() {
@Override
public void onSuccess(JSONObject json) {
TextView membersTextView = (TextView) findViewById(R.id.membersTextView);
JSONArray members = json.optJSONArray("members");
String output = "";
for (int i = 0; i < members.length(); i++) {
output += members.optString(i) + "; ";
}
membersTextView.setText(output);
}
@Override
public void onFinish() {
progressBar.setVisibility(View.GONE);
layoutView.setVisibility(View.VISIBLE);
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -30,7 +30,6 @@ import org.json.JSONObject;
* @author bgamard
*/
public class LoginActivity extends AppCompatActivity {
/**
* User interface.
*/

View File

@ -42,7 +42,6 @@ import org.json.JSONObject;
*/
public class MainActivity extends AppCompatActivity {
private ActionBarDrawerToggle drawerToggle;
private MenuItem searchItem;
private DrawerLayout drawerLayout;
@ -72,7 +71,7 @@ public class MainActivity extends AppCompatActivity {
// between the sliding drawer and the action bar app icon
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout,
R.string.drawer_open, R.string.drawer_close);
drawerLayout.setDrawerListener(drawerToggle);
drawerLayout.addDrawerListener(drawerToggle);
// Fill the drawer user info
JSONObject userInfo = ApplicationContext.getInstance().getUserInfo();
@ -137,6 +136,15 @@ public class MainActivity extends AppCompatActivity {
}
});
// Click on Latest activity
View auditLogLayout = findViewById(R.id.auditLogLayout);
auditLogLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, AuditLogActivity.class));
}
});
handleIntent(getIntent());
EventBus.getDefault().register(this);

View File

@ -12,7 +12,6 @@ import com.sismics.docs.fragment.SettingsFragment;
* @author bgamard.
*/
public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@ -0,0 +1,91 @@
package com.sismics.docs.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.sismics.docs.R;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.model.application.ApplicationContext;
import com.sismics.docs.resource.UserResource;
import org.json.JSONObject;
/**
* User profile activity.
*
* @author bgamard.
*/
public class UserProfileActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check if logged in
if (!ApplicationContext.getInstance().isLoggedIn()) {
startActivity(new Intent(this, LoginActivity.class));
finish();
return;
}
// Handle activity context
if (getIntent() == null) {
finish();
return;
}
// Input username
final String username = getIntent().getStringExtra("username");
if (username == null) {
finish();
return;
}
// Setup the activity
setTitle(username);
setContentView(R.layout.userprofile_activity);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
}
// Get the user and populate the view
final ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
final View layoutView = findViewById(R.id.layout);
progressBar.setVisibility(View.VISIBLE);
layoutView.setVisibility(View.GONE);
UserResource.get(this, username, new HttpCallback() {
@Override
public void onSuccess(JSONObject json) {
TextView emailTextView = (TextView) findViewById(R.id.emailTextView);
emailTextView.setText(json.optString("email"));
TextView quotaTextView = (TextView) findViewById(R.id.quotaTextView);
quotaTextView.setText(getString(R.string.storage_display,
Math.round(json.optLong("storage_current") / 1000000),
Math.round(json.optLong("storage_quota") / 1000000)));
}
@Override
public void onFinish() {
progressBar.setVisibility(View.GONE);
layoutView.setVisibility(View.VISIBLE);
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -98,11 +98,19 @@ public class AclListAdapter extends BaseAdapter {
* An ACL item in the list.
* Permissions are grouped together.
*/
private static class AclItem {
public static class AclItem {
private String type;
private String name;
private List<String> permList = new ArrayList<>();
public String getType() {
return type;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
return (type + name).hashCode();

View File

@ -0,0 +1,93 @@
package com.sismics.docs.adapter;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.sismics.docs.R;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
/**
* Audit log list adapter.
*
* @author bgamard.
*/
public class AuditLogListAdapter extends BaseAdapter {
/**
* Shares.
*/
private List<JSONObject> logList;
/**
* Audit log list adapter.
*
* @param logs Logs
*/
public AuditLogListAdapter(JSONArray logs) {
this.logList = new ArrayList<>();
for (int i = 0; i < logs.length(); i++) {
logList.add(logs.optJSONObject(i));
}
}
@Override
public int getCount() {
return logList.size();
}
@Override
public JSONObject getItem(int position) {
return logList.get(position);
}
@Override
public long getItemId(int position) {
return getItem(position).hashCode();
}
@Override
public View getView(int position, View view, final ViewGroup parent) {
if (view == null) {
LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = vi.inflate(R.layout.auditlog_list_item, parent, false);
}
// Build message
final JSONObject log = getItem(position);
StringBuilder message = new StringBuilder(log.optString("class"));
switch (log.optString("type")) {
case "CREATE": message.append(" created"); break;
case "UPDATE": message.append(" updated"); break;
case "DELETE": message.append(" deleted"); break;
}
switch (log.optString("class")) {
case "Document":
case "Acl":
case "Tag":
case "User":
case "Group":
message.append(" : ");
message.append(log.optString("message"));
break;
}
// Fill the view
TextView usernameTextView = (TextView) view.findViewById(R.id.usernameTextView);
TextView messageTextView = (TextView) view.findViewById(R.id.messageTextView);
usernameTextView.setText(log.optString("username"));
messageTextView.setText(message);
return view;
}
}

View File

@ -55,6 +55,7 @@ public class SearchFragment extends DialogFragment {
View view = inflater.inflate(R.layout.search_dialog, null);
final EditText searchEditText = (EditText) view.findViewById(R.id.searchEditText);
final EditText fulltextEditText = (EditText) view.findViewById(R.id.fulltextEditText);
final EditText creatorEditText = (EditText) view.findViewById(R.id.creatorEditText);
final CheckBox sharedCheckbox = (CheckBox) view.findViewById(R.id.sharedCheckbox);
final Spinner languageSpinner = (Spinner) view.findViewById(R.id.languageSpinner);
final DatePickerView beforeDatePicker = (DatePickerView) view.findViewById(R.id.beforeDatePicker);
@ -89,6 +90,7 @@ public class SearchFragment extends DialogFragment {
// Build the simple criterias
SearchQueryBuilder queryBuilder = new SearchQueryBuilder()
.simpleSearch(searchEditText.getText().toString())
.creator(creatorEditText.getText().toString())
.shared(sharedCheckbox.isChecked())
.language(((LanguageAdapter.Language) languageSpinner.getSelectedItem()).getId())
.before(beforeDatePicker.getDate())

View File

@ -0,0 +1,38 @@
package com.sismics.docs.resource;
import android.content.Context;
import com.sismics.docs.listener.HttpCallback;
import com.sismics.docs.util.OkHttpUtil;
import okhttp3.HttpUrl;
import okhttp3.Request;
/**
* Access to /auditlog API.
*
* @author bgamard
*/
public class AuditLogResource extends BaseResource {
/**
* GET /auditlog.
*
* @param context Context
* @param documentId Document ID
* @param callback Callback
*/
public static void list(Context context, String documentId, HttpCallback callback) {
HttpUrl.Builder httpUrlBuilder = HttpUrl.parse(getApiUrl(context) + "/auditlog")
.newBuilder();
if (documentId != null) {
httpUrlBuilder.addQueryParameter("document", documentId);
}
Request request = new Request.Builder()
.url(httpUrlBuilder.build())
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
}

View File

@ -54,6 +54,23 @@ public class UserResource extends BaseResource {
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
* GET /user/username.
*
* @param context Context
* param username Username
* @param callback Callback
*/
public static void get(Context context, String username, HttpCallback callback) {
Request request = new Request.Builder()
.url(HttpUrl.parse(getApiUrl(context) + "/user/" + username))
.get()
.build();
OkHttpUtil.buildClient(context)
.newCall(request)
.enqueue(HttpCallback.buildOkHttpCallback(callback));
}
/**
* POST /user/logout.
*

View File

@ -59,6 +59,21 @@ public class SearchQueryBuilder {
return this;
}
/**
* Add a creator criteria.
*
* @param creator Creator criteria
* @return The builder
*/
public SearchQueryBuilder creator(String creator) {
if (isValid(creator)) {
query.append(SEARCH_SEPARATOR)
.append("by:")
.append(creator);
}
return this;
}
/**
* Add a language criteria.
*

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/auditLogListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
android:dividerHeight="0dp"
android:visibility="gone">
</ListView>
</android.support.v4.widget.SwipeRefreshLayout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
android:layout_centerInParent="true"
android:indeterminate="true" />
</RelativeLayout>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/assignImageView"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_assignment_grey600_48dp"/>
<TextView
android:id="@+id/usernameTextView"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/assignImageView"
android:layout_toEndOf="@+id/assignImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textColor="#212121"
android:text="admin"
android:textSize="16sp"
android:ellipsize="end"
android:maxLines="1"/>
<TextView
android:id="@+id/messageTextView"
android:layout_below="@+id/usernameTextView"
android:layout_toRightOf="@+id/assignImageView"
android:layout_toEndOf="@+id/assignImageView"
android:layout_marginTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:textColor="#777777"
android:text="Document created : test doc 1"
android:textSize="16sp"
android:maxLines="1"
android:ellipsize="end"/>
<TextView
android:id="@+id/dateTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="2014-12-02"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:textColor="#777777"
android:fontFamily="sans-serif-light"/>
</RelativeLayout>

View File

@ -37,7 +37,7 @@
android:textSize="16sp"
android:layout_centerInParent="true"/>
<com.shamanland.fab.FloatingActionButton
<android.support.design.widget.FloatingActionButton
android:id="@+id/addDocumentButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -48,6 +48,6 @@
android:layout_marginEnd="16dp"
android:layout_marginBottom="20dp"
android:src="@drawable/ic_add_white_24dp"
app:floatingActionButtonColor="#263238"/>
app:fabSize="normal"/>
</RelativeLayout>

View File

@ -13,6 +13,7 @@
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:id="@+id/folderImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -22,7 +23,9 @@
android:id="@+id/titleTextView"
android:layout_alignParentTop="true"
android:layout_toRightOf="@+id/folderImageView"
android:layout_toEndOf="@+id/folderImageView"
android:layout_toLeftOf="@+id/dateTextView"
android:layout_toStartOf="@+id/dateTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"

View File

@ -52,14 +52,14 @@
<!-- Comments -->
<TextView
android:drawableStart="@drawable/ic_comment_black_24dp"
android:drawableLeft="@drawable/ic_comment_black_24dp"
android:drawableStart="@drawable/ic_comment_grey600_24dp"
android:drawableLeft="@drawable/ic_comment_grey600_24dp"
android:drawablePadding="6dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:gravity="center"
android:textColor="@color/primary_text_default_material_light"
android:textColor="#de000000"
android:text="@string/comments"
android:layout_margin="12dp"/>
@ -173,7 +173,7 @@
android:drawableTop="@drawable/ic_create_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/edit_document"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
@ -184,7 +184,7 @@
android:drawableTop="@drawable/ic_file_upload_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/upload_file"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
@ -195,7 +195,7 @@
android:drawableTop="@drawable/ic_file_download_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/download_document"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
@ -214,9 +214,9 @@
android:drawableTop="@drawable/ic_description_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/export_pdf"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
android:layout_margin="0dp"/>
<Button
android:id="@+id/actionSharing"
@ -225,9 +225,20 @@
android:drawableTop="@drawable/ic_share_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/share"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
android:layout_margin="0dp"/>
<Button
android:id="@+id/actionAuditLog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@drawable/ic_assignment_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/activity"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="0dp"/>
<Button
android:id="@+id/actionDelete"
@ -236,9 +247,9 @@
android:drawableTop="@drawable/ic_delete_grey600_24dp"
style="?android:buttonBarButtonStyle"
android:text="@string/delete_document"
android:textColor="@color/button_material_dark"
android:textColor="#ff5a595b"
android:textAllCaps="false"
android:layout_margin="8dp"/>
android:layout_margin="0dp"/>
</LinearLayout>
@ -280,12 +291,33 @@
android:layout_alignParentTop="true"
android:fontFamily="sans-serif-light"/>
<TextView
android:id="@+id/creatorLabel"
android:layout_width="100dp"
android:layout_height="24dp"
android:gravity="center_vertical"
android:layout_below="@+id/createdDateLabel"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:fontFamily="sans-serif"
android:text="@string/creator"/>
<TextView
android:id="@+id/creatorTextView"
android:layout_toRightOf="@id/creatorLabel"
android:layout_toEndOf="@id/creatorLabel"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:gravity="center_vertical"
android:layout_below="@+id/createdDateTextView"
android:fontFamily="sans-serif-light"/>
<TextView
android:id="@+id/tagTextView"
android:layout_marginTop="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/createdDateLabel"
android:layout_below="@id/creatorLabel"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:maxLines="1"
@ -333,7 +365,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/primary_text_default_material_light"
android:textColor="#de000000"
android:text="@string/who_can_access"
android:layout_margin="12dp"/>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:id="@+id/layout"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/membersTextView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
android:layout_centerInParent="true"
android:indeterminate="true" />
</RelativeLayout>

View File

@ -117,6 +117,40 @@
</RelativeLayout>
<!-- Audit log -->
<RelativeLayout
android:id="@+id/auditLogLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:clickable="true"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:id="@+id/auditLogImageView"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_marginRight="12dp"
android:layout_marginEnd="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_assignment_grey600_24dp"/>
<TextView
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/auditLogImageView"
android:layout_toEndOf="@+id/auditLogImageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:textColor="#212121"
android:text="@string/latest_activity"
android:textSize="14sp"/>
</RelativeLayout>
<!-- Separator -->
<View

View File

@ -27,6 +27,15 @@
android:textSize="18sp"
android:hint="@string/fulltext_search"/>
<!-- Creator -->
<EditText
android:id="@+id/creatorEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:textSize="18sp"
android:hint="@string/creator"/>
<!-- Language -->
<Spinner
android:id="@+id/languageSpinner"

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:id="@+id/layout"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:gravity="end"
android:textSize="16sp"
android:text="@string/email"/>
<TextView
android:id="@+id/emailTextView"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="16sp"
android:text="user1@sismicsdocs.com"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:textStyle="bold"
android:gravity="end"
android:textSize="16sp"
android:text="@string/storage_quota"/>
<TextView
android:id="@+id/quotaTextView"
android:layout_marginLeft="12dp"
android:layout_marginStart="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="16sp"
android:text="35/500 MB"/>
</LinearLayout>
</LinearLayout>
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
android:layout_centerInParent="true"
android:indeterminate="true" />
</RelativeLayout>

View File

@ -99,6 +99,7 @@
<string name="title">Title</string>
<string name="simple_search">Simple search</string>
<string name="fulltext_search">Fulltext search</string>
<string name="creator">Creator</string>
<string name="after_date">After date</string>
<string name="before_date">Before date</string>
<string name="search_tags">Search tags</string>
@ -115,7 +116,7 @@
<string name="comment_delete">Delete comment</string>
<string name="deleting_comment">Deleting comment</string>
<string name="error_deleting_comment">Error deleting comment</string>
<string name="export_pdf">Export PDF</string>
<string name="export_pdf">PDF</string>
<string name="download">Download</string>
<string name="margin">Margin</string>
<string name="fit_image_to_page">Fit image to page</string>
@ -125,5 +126,10 @@
<string name="download_file_title">Sismics Docs file export</string>
<string name="download_document_title">Sismics Docs document export</string>
<string name="download_pdf_title">Sismics Docs PDF export</string>
<string name="latest_activity">Latest activity</string>
<string name="activity">Activity</string>
<string name="email">E-mail</string>
<string name="storage_quota">Storage quota</string>
<string name="storage_display">%1$d/%2$d MB</string>
</resources>

View File

@ -68,10 +68,12 @@ public class AclDao {
@SuppressWarnings("unchecked")
public List<AclDto> getBySourceId(String sourceId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select a.ACL_ID_C, a.ACL_PERM_C, a.ACL_TARGETID_C, u.USE_USERNAME_C, s.SHA_NAME_C");
StringBuilder sb = new StringBuilder("select a.ACL_ID_C, a.ACL_PERM_C, a.ACL_TARGETID_C, ");
sb.append(" u.USE_USERNAME_C, s.SHA_ID_C, s.SHA_NAME_C, g.GRP_NAME_C ");
sb.append(" from T_ACL a ");
sb.append(" left join T_USER u on u.USE_ID_C = a.ACL_TARGETID_C ");
sb.append(" left join T_SHARE s on s.SHA_ID_C = a.ACL_TARGETID_C ");
sb.append(" left join T_GROUP g on g.GRP_ID_C = a.ACL_TARGETID_C ");
sb.append(" where a.ACL_DELETEDATE_D is null and a.ACL_SOURCEID_C = :sourceId ");
// Perform the query
@ -88,10 +90,21 @@ public class AclDao {
aclDto.setPerm(PermType.valueOf((String) o[i++]));
aclDto.setTargetId((String) o[i++]);
String userName = (String) o[i++];
String shareId = (String) o[i++];
String shareName = (String) o[i++];
aclDto.setTargetName(userName == null ? shareName : userName);
aclDto.setTargetType(userName == null ?
AclTargetType.SHARE.name() : AclTargetType.USER.name());
String groupName = (String) o[i++];
if (userName != null) {
aclDto.setTargetName(userName);
aclDto.setTargetType(AclTargetType.USER.name());
}
if (shareId != null) { // Use ID because share name is nullable
aclDto.setTargetName(shareName);
aclDto.setTargetType(AclTargetType.SHARE.name());
}
if (groupName != null) {
aclDto.setTargetName(groupName);
aclDto.setTargetType(AclTargetType.GROUP.name());
}
aclDtoList.add(aclDto);
}
return aclDtoList;
@ -105,12 +118,12 @@ public class AclDao {
* @param targetId ACL target entity ID
* @return True if the document is accessible
*/
public boolean checkPermission(String sourceId, PermType perm, String targetId) {
public boolean checkPermission(String sourceId, PermType perm, List<String> targetIdList) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select a from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId and a.deleteDate is null");
Query q = em.createQuery("select a from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId in (:targetIdList) and a.deleteDate is null");
q.setParameter("sourceId", sourceId);
q.setParameter("perm", perm);
q.setParameter("targetId", targetId);
q.setParameter("targetIdList", targetIdList);
// We have a matching permission
if (q.getResultList().size() > 0) {

View File

@ -54,9 +54,8 @@ public class AuditLogDao {
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of audit logs
* @throws Exception
*/
public void findByCriteria(PaginatedList<AuditLogDto> paginatedList, AuditLogCriteria criteria, SortCriteria sortCriteria) throws Exception {
public void findByCriteria(PaginatedList<AuditLogDto> paginatedList, AuditLogCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
StringBuilder baseQuery = new StringBuilder("select l.LOG_ID_C c0, l.LOG_CREATEDATE_D c1, u.USE_USERNAME_C c2, l.LOG_IDENTITY_C c3, l.LOG_CLASSENTITY_C c4, l.LOG_TYPE_C c5, l.LOG_MESSAGE_C c6 from T_AUDIT_LOG l ");

View File

@ -57,7 +57,7 @@ public class DocumentDao {
}
/**
* Returns the list of all documents.
* Returns the list of all active documents.
*
* @return List of documents
*/
@ -69,7 +69,7 @@ public class DocumentDao {
}
/**
* Returns the list of all documents from a user.
* Returns the list of all active documents from a user.
*
* @param userId User ID
* @return List of documents
@ -83,21 +83,29 @@ public class DocumentDao {
}
/**
* Returns an active document.
* Returns an active document with permission checking.
*
* @param id Document ID
* @param perm Permission needed
* @param userId User ID
* @return Document
*/
public DocumentDto getDocument(String id) {
public DocumentDto getDocument(String id, PermType perm, List<String> targetIdList) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_SUBJECT_C, d.DOC_IDENTIFIER_C, d.DOC_PUBLISHER_C, d.DOC_FORMAT_C, d.DOC_SOURCE_C, d.DOC_TYPE_C, d.DOC_COVERAGE_C, d.DOC_RIGHTS_C, d.DOC_CREATEDATE_D, d.DOC_LANGUAGE_C, ");
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C, d.DOC_TITLE_C, d.DOC_DESCRIPTION_C, d.DOC_SUBJECT_C, d.DOC_IDENTIFIER_C, d.DOC_PUBLISHER_C, d.DOC_FORMAT_C, d.DOC_SOURCE_C, d.DOC_TYPE_C, d.DOC_COVERAGE_C, d.DOC_RIGHTS_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 ");
sb.append(" from T_DOCUMENT d ");
sb.append(" join T_USER u on d.DOC_IDUSER_C = u.USE_ID_C ");
sb.append(" left join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C in (:targetIdList) and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null ");
sb.append(" where d.DOC_ID_C = :id and a.ACL_ID_C is not null and d.DOC_DELETEDATE_D is null ");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("id", id);
q.setParameter("perm", perm.name());
q.setParameter("targetIdList", targetIdList);
Object[] o = null;
try {
o = (Object[]) q.getSingleResult();
@ -126,30 +134,6 @@ public class DocumentDao {
return documentDto;
}
/**
* Returns an active document.
*
* @param id Document ID
* @param perm Permission needed
* @param userId User ID
* @return Document
*/
public Document getDocument(String id, PermType perm, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select d.* from T_DOCUMENT d ");
sb.append(" join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C = :userId and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null ");
sb.append(" where d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null");
Query q = em.createNativeQuery(sb.toString(), Document.class);
q.setParameter("id", id);
q.setParameter("perm", perm.name());
q.setParameter("userId", userId);
try {
return (Document) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes a document.
*
@ -184,20 +168,27 @@ public class DocumentDao {
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update Relation r set r.deleteDate = :dateNow where (r.fromDocumentId = :documentId or r.toDocumentId = :documentId) and r.deleteDate is not null");
q.setParameter("documentId", id);
q.setParameter("dateNow", dateNow);
q.executeUpdate();
// Create audit log
AuditLogUtil.create(documentDb, AuditLogType.DELETE, userId);
}
/**
* Gets a document by its ID.
* Gets an active document by its ID.
*
* @param id Document ID
* @return Document
*/
public Document getById(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");
q.setParameter("id", id);
try {
return em.find(Document.class, id);
return (Document) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
@ -216,16 +207,16 @@ public class DocumentDao {
Map<String, Object> parameterMap = new HashMap<String, Object>();
List<String> criteriaList = new ArrayList<String>();
StringBuilder sb = new StringBuilder("select d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, ");
StringBuilder sb = new StringBuilder("select distinct d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3, d.DOC_LANGUAGE_C c4, ");
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) c5, ");
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) c6 ");
sb.append(" from T_DOCUMENT d ");
// Adds search criteria
if (criteria.getUserId() != null) {
if (criteria.getTargetIdList() != null) {
// Read permission is enough for searching
sb.append(" join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C = :userId and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
parameterMap.put("userId", criteria.getUserId());
sb.append(" join T_ACL a on a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_TARGETID_C in (:targetIdList) and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
parameterMap.put("targetIdList", criteria.getTargetIdList());
}
if (!Strings.isNullOrEmpty(criteria.getSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) {
LuceneDao luceneDao = new LuceneDao();

View File

@ -0,0 +1,283 @@
package com.sismics.docs.core.dao.jpa;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria;
import com.sismics.docs.core.dao.jpa.dto.GroupDto;
import com.sismics.docs.core.model.jpa.Group;
import com.sismics.docs.core.model.jpa.UserGroup;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
/**
* Group DAO.
*
* @author bgamard
*/
public class GroupDao {
/**
* Returns a group by name.
*
* @param name Name
* @return Group
*/
public Group getActiveByName(String name) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select g from Group g where g.name = :name and g.deleteDate is null");
q.setParameter("name", name);
try {
return (Group) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Returns a group by ID.
*
* @param id Group ID
* @return Group
*/
public Group getActiveById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select g from Group g where g.id = :id and g.deleteDate is null");
q.setParameter("id", id);
try {
return (Group) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Creates a new group.
*
* @param group Group
* @param userId User ID
* @return New ID
* @throws Exception
*/
public String create(Group group, String userId) {
// Create the UUID
group.setId(UUID.randomUUID().toString());
// Create the group
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(group);
// Create audit log
AuditLogUtil.create(group, AuditLogType.CREATE, userId);
return group.getId();
}
/**
* Deletes a group.
*
* @param groupId Group ID
* @param userId User ID
*/
public void delete(String groupId, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the group
Query q = em.createQuery("select g from Group g where g.id = :id and g.deleteDate is null");
q.setParameter("id", groupId);
Group groupDb = (Group) q.getSingleResult();
// Delete the group
Date dateNow = new Date();
groupDb.setDeleteDate(dateNow);
// Delete linked data
q = em.createQuery("update UserGroup ug set ug.deleteDate = :dateNow where ug.groupId = :groupId and ug.deleteDate is not null");
q.setParameter("dateNow", dateNow);
q.setParameter("groupId", groupId);
q.executeUpdate();
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.targetId = :groupId and a.deleteDate is null");
q.setParameter("groupId", groupDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update Group g set g.parentId = null where g.parentId = :groupId and g.deleteDate is null");
q.setParameter("groupId", groupDb.getId());
q.executeUpdate();
// Create audit log
AuditLogUtil.create(groupDb, AuditLogType.DELETE, userId);
}
/**
* Add an user to a group.
*
* @param group Group
* @return New ID
* @throws Exception
*/
public String addMember(UserGroup userGroup) {
// Create the UUID
userGroup.setId(UUID.randomUUID().toString());
// Create the user group
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(userGroup);
return userGroup.getId();
}
/**
* Remove an user from a group.
*
* @param groupId Group ID
* @param userId User ID
*/
public void removeMember(String groupId, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user group
Query q = em.createQuery("select ug from UserGroup ug where ug.groupId = :groupId and ug.userId = :userId and ug.deleteDate is null");
q.setParameter("groupId", groupId);
q.setParameter("userId", userId);
UserGroup userGroupDb = (UserGroup) q.getSingleResult();
// Delete the user group
Date dateNow = new Date();
userGroupDb.setDeleteDate(dateNow);
}
/**
* Returns the list of all groups.
*
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of groups
*/
public List<GroupDto> findByCriteria(GroupCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
List<String> criteriaList = new ArrayList<String>();
StringBuilder sb = new StringBuilder("select g.GRP_ID_C as c0, g.GRP_NAME_C as c1, g.GRP_IDPARENT_C as c2, gp.GRP_NAME_C as c3, g.GRP_IDROLE_C ");
if (criteria.getUserId() != null) {
sb.append(" , ug.UGP_ID_C ");
}
sb.append(" from T_GROUP g ");
sb.append(" left join T_GROUP gp on g.GRP_IDPARENT_C = gp.GRP_ID_C ");
// Add search criterias
if (criteria.getSearch() != null) {
criteriaList.add("lower(g.GRP_NAME_C) like lower(:search)");
parameterMap.put("search", "%" + criteria.getSearch() + "%");
}
if (criteria.getUserId() != null) {
// Left join and post-filtering for recursive groups
sb.append((criteria.isRecursive() ? " left " : "")
+ " join T_USER_GROUP ug on ug.UGP_IDGROUP_C = g.GRP_ID_C and ug.UGP_IDUSER_C = :userId and ug.UGP_DELETEDATE_D is null ");
parameterMap.put("userId", criteria.getUserId());
}
criteriaList.add("g.GRP_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
// Perform the search
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
@SuppressWarnings("unchecked")
List<Object[]> l = QueryUtil.getNativeQuery(queryParam).getResultList();
// Assemble results
List<GroupDto> groupDtoList = new ArrayList<>();
List<GroupDto> userGroupDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
GroupDto groupDto = new GroupDto()
.setId((String) o[i++])
.setName((String) o[i++])
.setParentId((String) o[i++])
.setParentName((String) o[i++])
.setRoleId((String) o[i++]);
groupDtoList.add(groupDto);
if (criteria.getUserId() != null && o[i++] != null) {
userGroupDtoList.add(groupDto);
}
}
// Post-query filtering for recursive groups
if (criteria.getUserId() != null && criteria.isRecursive()) {
Set<GroupDto> filteredGroupDtoSet = new HashSet<>();
for (GroupDto userGroupDto : userGroupDtoList) {
filteredGroupDtoSet.add(userGroupDto); // Direct group
findGroupParentHierarchy(filteredGroupDtoSet, groupDtoList, userGroupDto, 0); // Indirect groups
}
groupDtoList = new ArrayList<>(filteredGroupDtoSet);
}
return groupDtoList;
}
/**
* Recursively search group's parents.
*
* @param parentGroupDtoSet Resulting parents
* @param groupDtoList All groups
* @param userGroupDto Reference group to search from
* @param depth Depth
*/
private void findGroupParentHierarchy(Set<GroupDto> parentGroupDtoSet, List<GroupDto> groupDtoList, GroupDto userGroupDto, int depth) {
if (userGroupDto.getParentId() == null || depth == 10) { // Max depth 10 to avoid infinite loop
return;
}
for (GroupDto groupDto : groupDtoList) {
if (groupDto.getId().equals(userGroupDto.getParentId())) {
parentGroupDtoSet.add(groupDto); // Add parent
findGroupParentHierarchy(parentGroupDtoSet, groupDtoList, groupDto, depth + 1); // Find parent's parents
}
}
}
/**
* Update a group.
*
* @param group Group to update
* @param userId User ID
* @return Updated group
*/
public Group update(Group group, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the group
Query q = em.createQuery("select g from Group g where g.id = :id and g.deleteDate is null");
q.setParameter("id", group.getId());
Group groupFromDb = (Group) q.getSingleResult();
// Update the group
groupFromDb.setName(group.getName());
groupFromDb.setParentId(group.getParentId());
// Create audit log
AuditLogUtil.create(groupFromDb, AuditLogType.UPDATE, userId);
return groupFromDb;
}
}

View File

@ -0,0 +1,99 @@
package com.sismics.docs.core.dao.jpa;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.sismics.docs.core.dao.jpa.dto.RelationDto;
import com.sismics.docs.core.model.jpa.Relation;
import com.sismics.util.context.ThreadLocalContext;
/**
* Relation DAO.
*
* @author bgamard
*/
public class RelationDao {
/**
* Get all relations from/to a document.
*
* @param documentId Document ID
* @return List of relations
*/
@SuppressWarnings("unchecked")
public List<RelationDto> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select d.DOC_ID_C, d.DOC_TITLE_C, r.REL_IDDOCFROM_C ");
sb.append(" from T_RELATION r ");
sb.append(" join T_DOCUMENT d on d.DOC_ID_C = r.REL_IDDOCFROM_C and r.REL_IDDOCFROM_C != :documentId or d.DOC_ID_C = r.REL_IDDOCTO_C and r.REL_IDDOCTO_C != :documentId ");
sb.append(" where (r.REL_IDDOCFROM_C = :documentId or r.REL_IDDOCTO_C = :documentId) ");
sb.append(" and r.REL_DELETEDATE_D is null ");
sb.append(" order by d.DOC_TITLE_C ");
// Perform the query
Query q = em.createNativeQuery(sb.toString());
q.setParameter("documentId", documentId);
List<Object[]> l = q.getResultList();
// Assemble results
List<RelationDto> relationDtoList = new ArrayList<RelationDto>();
for (Object[] o : l) {
int i = 0;
RelationDto relationDto = new RelationDto();
relationDto.setId((String) o[i++]);
relationDto.setTitle((String) o[i++]);
String fromDocId = (String) o[i++];
relationDto.setSource(documentId.equals(fromDocId));
relationDtoList.add(relationDto);
}
return relationDtoList;
}
/**
* Update relations on a document.
*
* @param documentId Document ID
* @param documentIdSet Set of document ID
*/
public void updateRelationList(String documentId, Set<String> documentIdSet) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get current relations from this document
Query q = em.createQuery("select r from Relation r where r.fromDocumentId = :documentId and r.deleteDate is null");
q.setParameter("documentId", documentId);
@SuppressWarnings("unchecked")
List<Relation> relationList = q.getResultList();
// Deleting relations no longer there
for (Relation relation : relationList) {
if (!documentIdSet.contains(relation.getToDocumentId())) {
relation.setDeleteDate(new Date());
}
}
// Adding new relations
for (String targetDocId : documentIdSet) {
boolean found = false;
for (Relation relation : relationList) {
if (relation.getToDocumentId().equals(targetDocId)) {
found = true;
break;
}
}
if (!found) {
Relation relation = new Relation();
relation.setId(UUID.randomUUID().toString());
relation.setFromDocumentId(documentId);
relation.setToDocumentId(targetDocId);
em.persist(relation);
}
}
}
}

View File

@ -16,17 +16,17 @@ public class RoleBaseFunctionDao {
/**
* Find the set of base functions of a role.
*
* @param roleId Role ID
* @param roleIdSet Set of role ID
* @return Set of base functions
*/
@SuppressWarnings("unchecked")
public Set<String> findByRoleId(String roleId) {
public Set<String> findByRoleId(Set<String> roleIdSet) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select rbf.RBF_IDBASEFUNCTION_C from T_ROLE_BASE_FUNCTION rbf, T_ROLE r");
sb.append(" where rbf.RBF_IDROLE_C = :roleId and rbf.RBF_DELETEDATE_D is null");
sb.append(" where rbf.RBF_IDROLE_C in (:roleIdSet) and rbf.RBF_DELETEDATE_D is null");
sb.append(" and r.ROL_ID_C = rbf.RBF_IDROLE_C and r.ROL_DELETEDATE_D is null");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("roleId", roleId);
q.setParameter("roleIdSet", roleIdSet);
return Sets.newHashSet(q.getResultList());
}
}

View File

@ -20,9 +20,8 @@ import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
import com.sismics.docs.core.dao.jpa.dto.UserDto;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
@ -265,10 +264,11 @@ public class UserDao {
/**
* Returns the list of all users.
*
* @param paginatedList List of users (updated by side effects)
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of users
*/
public void findByCriteria(PaginatedList<UserDto> paginatedList, UserCriteria criteria, SortCriteria sortCriteria) {
public List<UserDto> findByCriteria(UserCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
List<String> criteriaList = new ArrayList<String>();
@ -281,6 +281,11 @@ public class UserDao {
parameterMap.put("search", "%" + criteria.getSearch() + "%");
}
if (criteria.getGroupId() != null) {
sb.append(" join T_USER_GROUP ug on ug.UGP_IDUSER_C = u.USE_ID_C and ug.UGP_IDGROUP_C = :groupId and ug.UGP_DELETEDATE_D is null ");
parameterMap.put("groupId", criteria.getGroupId());
}
criteriaList.add("u.USE_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
@ -289,8 +294,9 @@ public class UserDao {
}
// Perform the search
QueryParam queryParam = new QueryParam(sb.toString(), parameterMap);
List<Object[]> l = PaginatedLists.executePaginatedQuery(paginatedList, queryParam, sortCriteria);
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
@SuppressWarnings("unchecked")
List<Object[]> l = QueryUtil.getNativeQuery(queryParam).getResultList();
// Assemble results
List<UserDto> userDtoList = new ArrayList<UserDto>();
@ -305,6 +311,6 @@ public class UserDao {
userDto.setStorageQuota(((Number) o[i++]).longValue());
userDtoList.add(userDto);
}
paginatedList.setResultList(userDtoList);
return userDtoList;
}
}

View File

@ -35,7 +35,7 @@ public class VocabularyDao {
}
/**
* Get all vocabulary entries sharing a single name
* Get all vocabulary entries sharing a single name.
*
* @param name Name
* @return Vocabulary entries

View File

@ -11,9 +11,9 @@ import java.util.List;
*/
public class DocumentCriteria {
/**
* User ID.
* ACL target ID list.
*/
private String userId;
private List<String> targetIdList;
/**
* Search query.
@ -55,12 +55,12 @@ public class DocumentCriteria {
*/
private String creatorId;
public String getUserId() {
return userId;
public List<String> getTargetIdList() {
return targetIdList;
}
public void setUserId(String userId) {
this.userId = userId;
public void setTargetIdList(List<String> targetIdList) {
this.targetIdList = targetIdList;
}
public String getSearch() {

View File

@ -0,0 +1,50 @@
package com.sismics.docs.core.dao.jpa.criteria;
/**
* Group criteria.
*
* @author bgamard
*/
public class GroupCriteria {
/**
* Search query.
*/
private String search;
/**
* User ID.
*/
private String userId;
/**
* Retrieve user groups recursively.
*/
private boolean recursive = false;
public String getSearch() {
return search;
}
public GroupCriteria setSearch(String search) {
this.search = search;
return this;
}
public String getUserId() {
return userId;
}
public GroupCriteria setUserId(String userId) {
this.userId = userId;
return this;
}
public boolean isRecursive() {
return recursive;
}
public GroupCriteria setRecursive(boolean recursive) {
this.recursive = recursive;
return this;
}
}

View File

@ -1,7 +1,5 @@
package com.sismics.docs.core.dao.jpa.criteria;
/**
* User criteria.
*
@ -13,6 +11,11 @@ public class UserCriteria {
*/
private String search;
/**
* Group ID.
*/
private String groupId;
public String getSearch() {
return search;
}
@ -21,4 +24,13 @@ public class UserCriteria {
this.search = search;
return this;
}
public String getGroupId() {
return groupId;
}
public UserCriteria setGroupId(String groupId) {
this.groupId = groupId;
return this;
}
}

View File

@ -0,0 +1,88 @@
package com.sismics.docs.core.dao.jpa.dto;
/**
* Group DTO.
*
* @author bgamard
*/
public class GroupDto {
/**
* Group ID.
*/
private String id;
/**
* Name.
*/
private String name;
/**
* Parent ID.
*/
private String parentId;
/**
* Parent name.
*/
private String parentName;
/**
* Role ID.
*/
private String roleId;
public String getId() {
return id;
}
public GroupDto setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public GroupDto setName(String name) {
this.name = name;
return this;
}
public String getParentId() {
return parentId;
}
public GroupDto setParentId(String parentId) {
this.parentId = parentId;
return this;
}
public String getParentName() {
return parentName;
}
public GroupDto setParentName(String parentName) {
this.parentName = parentName;
return this;
}
public String getRoleId() {
return roleId;
}
public GroupDto setRoleId(String roleId) {
this.roleId = roleId;
return this;
}
@Override
public boolean equals(Object obj) {
return id.equals(((GroupDto) obj).getId());
}
@Override
public int hashCode() {
return id.hashCode();
}
}

View File

@ -0,0 +1,47 @@
package com.sismics.docs.core.dao.jpa.dto;
/**
* Tag DTO.
*
* @author bgamard
*/
public class RelationDto {
/**
* Document ID.
*/
private String id;
/**
* Document title.
*/
private String title;
/**
* True if the document is the source of the relation.
*/
private boolean source;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isSource() {
return source;
}
public void setSource(boolean source) {
this.source = source;
}
}

View File

@ -26,74 +26,34 @@ public class TagDto {
*/
private String parentId;
/**
* Getter of id.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
/**
* Getter of name.
*
* @return the name
*/
public String getName() {
return name;
}
/**
* Setter of name.
*
* @param name name
*/
public void setName(String name) {
this.name = name;
}
/**
* Getter of color.
*
* @return the color
*/
public String getColor() {
return color;
}
/**
* Setter of color.
*
* @param color color
*/
public void setColor(String color) {
this.color = color;
}
/**
* Getter of parentId.
*
* @return the parentId
*/
public String getParentId() {
return parentId;
}
/**
* Setter of parentId.
*
* @param color parentId
*/
public void setParentId(String parentId) {
this.parentId = parentId;
}

View File

@ -1,6 +1,5 @@
package com.sismics.docs.core.dao.jpa.dto;
/**
* User DTO.
*

View File

@ -1,9 +1,7 @@
package com.sismics.docs.core.dao.lucene;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
@ -46,16 +44,14 @@ public class LuceneDao {
indexWriter.deleteAll();
// Add all documents
Map<String, Document> documentMap = new HashMap<>();
for (Document document : documentList) {
org.apache.lucene.document.Document luceneDocument = getDocumentFromDocument(document);
indexWriter.addDocument(luceneDocument);
documentMap.put(document.getId(), document);
}
// Add all files
for (File file : fileList) {
org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file, documentMap.get(file.getDocumentId()));
org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file);
indexWriter.addDocument(luceneDocument);
}
}
@ -81,13 +77,12 @@ public class LuceneDao {
* Add file to the index.
*
* @param file File to add
* @param document Document linked to the file
*/
public void createFile(final File file, final Document document) {
public void createFile(final File file) {
LuceneUtil.handle(new LuceneRunnable() {
@Override
public void run(IndexWriter indexWriter) throws Exception {
org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file, document);
org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file);
indexWriter.addDocument(luceneDocument);
}
});
@ -112,13 +107,12 @@ public class LuceneDao {
* Update file index.
*
* @param file Updated file
* @param document Document linked to the file
*/
public void updateFile(final File file, final Document document) {
public void updateFile(final File file) {
LuceneUtil.handle(new LuceneRunnable() {
@Override
public void run(IndexWriter indexWriter) throws Exception {
org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file, document);
org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file);
indexWriter.updateDocument(new Term("id", file.getId()), luceneDocument);
}
});
@ -200,7 +194,7 @@ public class LuceneDao {
/**
* Build Lucene document from database document.
*
* @param document Document
* @param documentDto Document
* @return Document
*/
private org.apache.lucene.document.Document getDocumentFromDocument(Document document) {
@ -243,10 +237,9 @@ public class LuceneDao {
* Build Lucene document from file.
*
* @param file File
* @param document Document linked to the file
* @return Document
*/
private org.apache.lucene.document.Document getDocumentFromFile(File file, Document document) {
private org.apache.lucene.document.Document getDocumentFromFile(File file) {
org.apache.lucene.document.Document luceneDocument = new org.apache.lucene.document.Document();
luceneDocument.add(new StringField("id", file.getId(), Field.Store.YES));
luceneDocument.add(new StringField("doctype", "file", Field.Store.YES));

View File

@ -1,7 +1,6 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.Document;
/**
* Document deleted event.
@ -10,32 +9,22 @@ import com.sismics.docs.core.model.jpa.Document;
*/
public class DocumentDeletedAsyncEvent extends UserEvent {
/**
* Created document.
* Document ID.
*/
private Document document;
private String documentId;
/**
* Getter of document.
*
* @return the document
*/
public Document getDocument() {
return document;
public String getDocumentId() {
return documentId;
}
/**
* Setter of document.
*
* @param document document
*/
public void setDocument(Document document) {
this.document = document;
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("document", document)
.add("documentId", documentId)
.toString();
}
}

View File

@ -1,7 +1,6 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.Document;
/**
* Document updated event.
@ -10,32 +9,22 @@ import com.sismics.docs.core.model.jpa.Document;
*/
public class DocumentUpdatedAsyncEvent extends UserEvent {
/**
* Created document.
* Document ID.
*/
private Document document;
private String documentId;
/**
* Getter of document.
*
* @return the document
*/
public Document getDocument() {
return document;
public String getDocumentId() {
return documentId;
}
/**
* Setter of document.
*
* @param document document
*/
public void setDocument(Document document) {
this.document = document;
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("document", document)
.add("documentId", documentId)
.toString();
}
}

View File

@ -3,7 +3,6 @@ package com.sismics.docs.core.event;
import java.io.InputStream;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.File;
/**
@ -18,9 +17,9 @@ public class FileCreatedAsyncEvent extends UserEvent {
private File file;
/**
* Document linked to the file.
* Language of the file.
*/
private Document document;
private String language;
/**
* Unencrypted input stream containing the file.
@ -42,12 +41,12 @@ public class FileCreatedAsyncEvent extends UserEvent {
this.file = file;
}
public Document getDocument() {
return document;
public String getLanguage() {
return language;
}
public void setDocument(Document document) {
this.document = document;
public void setLanguage(String language) {
this.language = language;
}
public InputStream getInputStream() {
@ -70,7 +69,7 @@ public class FileCreatedAsyncEvent extends UserEvent {
public String toString() {
return MoreObjects.toStringHelper(this)
.add("file", file)
.add("document", document)
.add("language", language)
.toString();
}
}

View File

@ -14,20 +14,10 @@ public class FileDeletedAsyncEvent extends UserEvent {
*/
private File file;
/**
* Getter of file.
*
* @return the file
*/
public File getFile() {
return file;
}
/**
* Setter of file.
*
* @param file file
*/
public void setFile(File file) {
this.file = file;
}

View File

@ -32,6 +32,6 @@ public class DocumentDeletedAsyncListener {
// Update Lucene index
LuceneDao luceneDao = new LuceneDao();
luceneDao.deleteDocument(documentDeletedAsyncEvent.getDocument().getId());
luceneDao.deleteDocument(documentDeletedAsyncEvent.getDocumentId());
}
}

View File

@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.jpa.ContributorDao;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.lucene.LuceneDao;
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
import com.sismics.docs.core.model.jpa.Contributor;
@ -35,12 +36,17 @@ public class DocumentUpdatedAsyncListener {
log.info("Document updated event: " + event.toString());
}
// Update contributors list
TransactionUtil.handle(new Runnable() {
@Override
public void run() {
// Update Lucene index
DocumentDao documentDao = new DocumentDao();
LuceneDao luceneDao = new LuceneDao();
luceneDao.updateDocument(documentDao.getById(event.getDocumentId()));
// Update contributors list
ContributorDao contributorDao = new ContributorDao();
List<Contributor> contributorList = contributorDao.findByDocumentId(event.getDocument().getId());
List<Contributor> contributorList = contributorDao.findByDocumentId(event.getDocumentId());
// Check if the user firing this event is not already a contributor
for (Contributor contributor : contributorList) {
@ -52,14 +58,10 @@ public class DocumentUpdatedAsyncListener {
// Add a new contributor
Contributor contributor = new Contributor();
contributor.setDocumentId(event.getDocument().getId());
contributor.setDocumentId(event.getDocumentId());
contributor.setUserId(event.getUserId());
contributorDao.create(contributor);
}
});
// Update Lucene index
LuceneDao luceneDao = new LuceneDao();
luceneDao.updateDocument(event.getDocument());
}
}

View File

@ -41,7 +41,7 @@ public class FileCreatedAsyncListener {
// Extract text content from the file
long startTime = System.currentTimeMillis();
final String content = FileUtil.extractContent(fileCreatedAsyncEvent.getDocument(), file,
final String content = FileUtil.extractContent(fileCreatedAsyncEvent.getLanguage(), file,
fileCreatedAsyncEvent.getInputStream(), fileCreatedAsyncEvent.getPdfInputStream());
fileCreatedAsyncEvent.getInputStream().close();
if (fileCreatedAsyncEvent.getPdfInputStream() != null) {
@ -66,6 +66,6 @@ public class FileCreatedAsyncListener {
// Update Lucene index
LuceneDao luceneDao = new LuceneDao();
luceneDao.createFile(fileCreatedAsyncEvent.getFile(), fileCreatedAsyncEvent.getDocument());
luceneDao.createFile(fileCreatedAsyncEvent.getFile());
}
}

View File

@ -30,20 +30,20 @@ public class Acl implements Loggable {
/**
* ACL permission.
*/
@Column(name = "ACL_PERM_C", length = 30)
@Column(name = "ACL_PERM_C", length = 30, nullable = false)
@Enumerated(EnumType.STRING)
private PermType perm;
/**
* ACL source ID.
*/
@Column(name = "ACL_SOURCEID_C", length = 36)
@Column(name = "ACL_SOURCEID_C", length = 36, nullable = false)
private String sourceId;
/**
* ACL target ID.
*/
@Column(name = "ACL_TARGETID_C", length = 36)
@Column(name = "ACL_TARGETID_C", length = 36, nullable = false)
private String targetId;
/**

View File

@ -13,7 +13,7 @@ import com.google.common.base.MoreObjects;
* @author bgamard
*/
@Entity
@Table(name = "T_Contributor")
@Table(name = "T_CONTRIBUTOR")
public class Contributor {
/**
* Contributor ID.

View File

@ -33,13 +33,13 @@ public class DocumentTag implements Serializable {
/**
* Document ID.
*/
@Column(name = "DOT_IDDOCUMENT_C", length = 36)
@Column(name = "DOT_IDDOCUMENT_C", nullable = false, length = 36)
private String documentId;
/**
* Tag ID.
*/
@Column(name = "DOT_IDTAG_C", length = 36)
@Column(name = "DOT_IDTAG_C", nullable = false, length = 36)
private String tagId;
/**
@ -83,6 +83,7 @@ public class DocumentTag implements Serializable {
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("documentId", documentId)
.add("tagId", tagId)
.toString();

View File

@ -0,0 +1,111 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.MoreObjects;
/**
* Group entity.
*
* @author bgamard
*/
@Entity
@Table(name = "T_GROUP")
public class Group implements Loggable {
/**
* Group ID.
*/
@Id
@Column(name = "GRP_ID_C", nullable = false, length = 36)
private String id;
/**
* Vocabulary value.
*/
@Column(name = "GRP_IDPARENT_C", length = 36)
private String parentId;
/**
* Group name.
*/
@Column(name = "GRP_NAME_C", nullable = false, length = 50)
private String name;
/**
* Role ID.
*/
@Column(name = "GRP_IDROLE_C", length = 36)
private String roleId;
/**
* Deletion date.
*/
@Column(name = "GRP_DELETEDATE_D")
private Date deleteDate;
public String getId() {
return id;
}
public Group setId(String id) {
this.id = id;
return this;
}
public String getParentId() {
return parentId;
}
public Group setParentId(String parentId) {
this.parentId = parentId;
return this;
}
public String getName() {
return name;
}
public Group setName(String name) {
this.name = name;
return this;
}
@Override
public Date getDeleteDate() {
return deleteDate;
}
public Group setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
return this;
}
public String getRoleId() {
return roleId;
}
public Group setRoleId(String roleId) {
this.roleId = roleId;
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("roleId", roleId)
.add("parentId", parentId)
.add("name", name)
.toString();
}
@Override
public String toMessage() {
return name;
}
}

View File

@ -0,0 +1,85 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.MoreObjects;
/**
* Relation entity.
*
* @author bgamard
*/
@Entity
@Table(name = "T_RELATION")
public class Relation {
/**
* Relation ID.
*/
@Id
@Column(name = "REL_ID_C", length = 36)
private String id;
/**
* Source document ID.
*/
@Column(name = "REL_IDDOCFROM_C", length = 36, nullable = false)
private String fromDocumentId;
/**
* Destination document ID.
*/
@Column(name = "REL_IDDOCTO_C", length = 36, nullable = false)
private String toDocumentId;
/**
* Deletion date.
*/
@Column(name = "REL_DELETEDATE_D")
private Date deleteDate;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getFromDocumentId() {
return fromDocumentId;
}
public void setFromDocumentId(String fromDocumentId) {
this.fromDocumentId = fromDocumentId;
}
public String getToDocumentId() {
return toDocumentId;
}
public void setToDocumentId(String toDocumentId) {
this.toDocumentId = toDocumentId;
}
public Date getDeleteDate() {
return deleteDate;
}
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("fromDocumentId", fromDocumentId)
.add("toDocumentId", toDocumentId)
.toString();
}
}

View File

@ -0,0 +1,91 @@
package com.sismics.docs.core.model.jpa;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.MoreObjects;
/**
* Link between an user and a group.
*
* @author bgamard
*/
@Entity
@Table(name = "T_USER_GROUP")
public class UserGroup implements Serializable {
/**
* Serial version UID.
*/
private static final long serialVersionUID = 1L;
/**
* User group ID.
*/
@Id
@Column(name = "UGP_ID_C", length = 36)
private String id;
/**
* User ID.
*/
@Column(name = "UGP_IDUSER_C", nullable = false, length = 36)
private String userId;
/**
* Group ID.
*/
@Column(name = "UGP_IDGROUP_C", nullable = false, length = 36)
private String groupId;
/**
* Deletion date.
*/
@Column(name = "UGP_DELETEDATE_D")
private Date deleteDate;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getGroupId() {
return groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public Date getDeleteDate() {
return deleteDate;
}
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("userId", userId)
.add("groupId", groupId)
.toString();
}
}

View File

@ -18,7 +18,6 @@ import org.imgscalr.Scalr.Mode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.tess4j.Tesseract;
import com.sismics.util.ImageUtil;
@ -37,17 +36,17 @@ public class FileUtil {
/**
* Extract content from a file.
*
* @param document Document linked to the file
* @param language Language to extract
* @param file File to extract
* @param inputStream Unencrypted input stream
* @param pdfInputStream Unencrypted PDF input stream
* @return Content extract
*/
public static String extractContent(Document document, File file, InputStream inputStream, InputStream pdfInputStream) {
public static String extractContent(String language, File file, InputStream inputStream, InputStream pdfInputStream) {
String content = null;
if (ImageUtil.isImage(file.getMimeType())) {
content = ocrFile(inputStream, document);
content = ocrFile(inputStream, language);
} else if (pdfInputStream != null) {
content = PdfUtil.extractPdf(pdfInputStream);
}
@ -59,10 +58,10 @@ public class FileUtil {
* Optical character recognition on a stream.
*
* @param inputStream Unencrypted input stream
* @param document Document linked to the file
* @param language Language to OCR
* @return Content extracted
*/
private static String ocrFile(InputStream inputStream, Document document) {
private static String ocrFile(InputStream inputStream, String language) {
Tesseract instance = Tesseract.getInstance();
String content = null;
BufferedImage image = null;
@ -80,7 +79,7 @@ public class FileUtil {
// OCR the file
try {
log.info("Starting OCR with TESSDATA_PREFIX=" + System.getenv("TESSDATA_PREFIX") + ";LC_NUMERIC=" + System.getenv("LC_NUMERIC"));
instance.setLanguage(document.getLanguage());
instance.setLanguage(language);
content = instance.doOCR(image);
} catch (Throwable e) {
log.error("Error while OCR-izing the image", e);

View File

@ -7,6 +7,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.imageio.ImageIO;
@ -17,6 +19,7 @@ import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
@ -29,8 +32,11 @@ import org.odftoolkit.odfdom.doc.OdfTextDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Strings;
import com.google.common.io.Closer;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.util.pdf.PdfPage;
import com.sismics.util.ImageUtil;
import com.sismics.util.mime.MimeType;
@ -141,25 +147,67 @@ public class PdfUtil {
/**
* Convert a document and its files to a merged PDF file.
*
* @param documentDto Document DTO
* @param fileList List of files
* @param fitImageToPage Fit images to the page
* @param metadata Add a page with metadata
* @param margin Margins in millimeters
* @return PDF input stream
* @throws IOException
*/
public static InputStream convertToPdf(List<File> fileList, boolean fitImageToPage, int margin) throws Exception {
// TODO PDF Export: Option to add a front page with:
// document title, document description, creator, date created, language,
// list of all files (and information if it is in this document or not)
// TODO PDF Export: Option to add the comments
// Create a blank PDF
public static InputStream convertToPdf(DocumentDto documentDto, List<File> fileList,
boolean fitImageToPage, boolean metadata, int margin) throws Exception {
// Setup PDFBox
Closer closer = Closer.create();
MemoryUsageSetting memUsageSettings = MemoryUsageSetting.setupMixed(1000000); // 1MB max memory usage
memUsageSettings.setTempDir(new java.io.File(System.getProperty("java.io.tmpdir"))); // To OS temp
float mmPerInch = 1 / (10 * 2.54f) * 72f;
// Create a blank PDF
try (PDDocument doc = new PDDocument(memUsageSettings)) {
// Add metadata
if (metadata) {
PDPage page = new PDPage();
doc.addPage(page);
try (PdfPage pdfPage = new PdfPage(doc, page, margin * mmPerInch, PDType1Font.HELVETICA, 12)) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
pdfPage.addText(documentDto.getTitle(), true, PDType1Font.HELVETICA_BOLD, 16)
.newLine()
.addText("Created by " + documentDto.getCreator()
+ " on " + dateFormat.format(new Date(documentDto.getCreateTimestamp())), true)
.newLine()
.addText(documentDto.getDescription())
.newLine();
if (!Strings.isNullOrEmpty(documentDto.getSubject())) {
pdfPage.addText("Subject: " + documentDto.getSubject());
}
if (!Strings.isNullOrEmpty(documentDto.getIdentifier())) {
pdfPage.addText("Identifier: " + documentDto.getIdentifier());
}
if (!Strings.isNullOrEmpty(documentDto.getPublisher())) {
pdfPage.addText("Publisher: " + documentDto.getPublisher());
}
if (!Strings.isNullOrEmpty(documentDto.getFormat())) {
pdfPage.addText("Format: " + documentDto.getFormat());
}
if (!Strings.isNullOrEmpty(documentDto.getSource())) {
pdfPage.addText("Source: " + documentDto.getSource());
}
if (!Strings.isNullOrEmpty(documentDto.getType())) {
pdfPage.addText("Type: " + documentDto.getType());
}
if (!Strings.isNullOrEmpty(documentDto.getCoverage())) {
pdfPage.addText("Coverage: " + documentDto.getCoverage());
}
if (!Strings.isNullOrEmpty(documentDto.getRights())) {
pdfPage.addText("Rights: " + documentDto.getRights());
}
pdfPage.addText("Language: " + documentDto.getLanguage())
.newLine()
.addText("Files in this document : " + fileList.size(), false, PDType1Font.HELVETICA_BOLD, 12);
}
}
// Add files
for (File file : fileList) {
Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId());

View File

@ -105,13 +105,7 @@ public class PaginatedLists {
* @return List of results
*/
public static <E> List<Object[]> executePaginatedQuery(PaginatedList<E> paginatedList, QueryParam queryParam, SortCriteria sortCriteria) {
StringBuilder sb = new StringBuilder(queryParam.getQueryString());
sb.append(" order by c");
sb.append(sortCriteria.getColumn());
sb.append(sortCriteria.isAsc() ? " asc" : " desc");
QueryParam sortedQueryParam = new QueryParam(sb.toString(), queryParam.getParameterMap());
QueryParam sortedQueryParam = QueryUtil.getSortedQueryParam(queryParam, sortCriteria);
executeCountQuery(paginatedList, sortedQueryParam);
return executeResultQuery(paginatedList, sortedQueryParam);
}

View File

@ -1,10 +1,11 @@
package com.sismics.docs.core.util.jpa;
import com.sismics.util.context.ThreadLocalContext;
import java.util.Map.Entry;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.Map.Entry;
import com.sismics.util.context.ThreadLocalContext;
/**
* Query utilities.
@ -27,4 +28,22 @@ public class QueryUtil {
}
return query;
}
/**
* Returns sorted query parameters.
*
* @param queryParam Query parameters
* @param sortCriteria Sort criteria
* @return Sorted query parameters
*/
public static QueryParam getSortedQueryParam(QueryParam queryParam, SortCriteria sortCriteria) {
StringBuilder sb = new StringBuilder(queryParam.getQueryString());
if (sortCriteria != null) {
sb.append(" order by c");
sb.append(sortCriteria.getColumn());
sb.append(sortCriteria.isAsc() ? " asc" : " desc");
}
return new QueryParam(sb.toString(), queryParam.getParameterMap());
}
}

View File

@ -0,0 +1,153 @@
package com.sismics.docs.core.util.pdf;
import java.io.Closeable;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
/**
* Wrapper around PDFBox for high level abstraction of PDF writing.
*
* @author bgamard
*/
public class PdfPage implements Closeable {
private PDPage pdPage;
private PDPageContentStream pdContent;
private float margin;
private PDFont defaultFont;
private int defaultFontSize;
/**
* Create a wrapper around a PDF page.
*
* @param pdDoc Document
* @param pdPage Page
* @param margin Margin
* @param defaultFont Default font
* @param defaultFontSize Default fond size
* @throws IOException
*/
public PdfPage(PDDocument pdDoc, PDPage pdPage, float margin, PDFont defaultFont, int defaultFontSize) throws IOException {
this.pdPage = pdPage;
this.pdContent = new PDPageContentStream(pdDoc, pdPage);
this.margin = margin;
this.defaultFont = defaultFont;
this.defaultFontSize = defaultFontSize;
pdContent.beginText();
pdContent.newLineAtOffset(margin, pdPage.getMediaBox().getHeight() - margin);
}
/**
* Write a text with default font.
*
* @param text Text
* @throws IOException
*/
public PdfPage addText(String text) throws IOException {
drawText(pdPage.getMediaBox().getWidth() - 2 * margin, defaultFont, defaultFontSize, text, false);
return this;
}
/**
* Write a text with default font.
*
* @param text Text
* @param centered If true, the text will be centered in the page
* @throws IOException
*/
public PdfPage addText(String text, boolean centered) throws IOException {
drawText(pdPage.getMediaBox().getWidth() - 2 * margin, defaultFont, defaultFontSize, text, centered);
return this;
}
/**
* Write a text in the page.
*
* @param text Text
* @param centered If true, the text will be centered in the page
* @param font Font
* @param fontSize Font size
* @throws IOException
*/
public PdfPage addText(String text, boolean centered, PDFont font, int fontSize) throws IOException {
drawText(pdPage.getMediaBox().getWidth() - 2 * margin, font, fontSize, text, centered);
return this;
}
/**
* Create a new line.
*
* @throws IOException
*/
public PdfPage newLine() throws IOException {
pdContent.newLineAtOffset(0, - defaultFont.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * defaultFontSize);
return this;
}
/**
* Draw a text with low level PDFBox API.
*
* @param paragraphWidth Paragraph width
* @param font Font
* @param fontSize Font size
* @param text Text
* @param centered If true, the text will be centered in the paragraph
* @throws IOException
*/
private void drawText(float paragraphWidth, PDFont font, int fontSize, String text, boolean centered) throws IOException {
pdContent.setFont(font, fontSize);
int start = 0;
int end = 0;
float height = font.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;
for (int i : possibleWrapPoints(text)) {
float width = font.getStringWidth(text.substring(start, i)) / 1000 * fontSize;
if (start < end && width > paragraphWidth) {
// Draw partial text and increase height
pdContent.newLineAtOffset(0, - height);
String line = text.substring(start, end);
float lineWidth = font.getStringWidth(line) / 1000 * fontSize;
float offset = (paragraphWidth - lineWidth) / 2;
if (centered) pdContent.newLineAtOffset(offset, 0);
pdContent.showText(line);
if (centered) pdContent.newLineAtOffset(- offset, 0);
start = end;
}
end = i;
}
// Last piece of text
String line = text.substring(start);
float lineWidth = font.getStringWidth(line) / 1000 * fontSize;
float offset = (paragraphWidth - lineWidth) / 2;
pdContent.newLineAtOffset(0, - height);
if (centered) pdContent.newLineAtOffset(offset, 0);
pdContent.showText(line);
if (centered) pdContent.newLineAtOffset(- offset, 0);
}
/**
* Returns wrap points for a given piece of text.
*
* @param text Text
* @return Wrap points
*/
private int[] possibleWrapPoints(String text) {
String[] split = text.split("(?<=\\W)");
int[] ret = new int[split.length];
ret[0] = split[0].length();
for (int i = 1 ; i < split.length ; i++) {
ret[i] = ret[i-1] + split[i].length();
}
return ret;
}
@Override
public void close() throws IOException {
pdContent.endText();
pdContent.close();
}
}

View File

@ -1,6 +1,7 @@
package com.sismics.util.log4j;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Level;
/**
* Log search criteria.
@ -10,9 +11,9 @@ import org.apache.commons.lang.StringUtils;
public class LogCriteria {
/**
* Logging level (DEBUG, WARN)...
* Minimum logging level (DEBUG, WARN)...
*/
private String level;
private Level minLevel;
/**
* Logger name / tag.
@ -24,57 +25,30 @@ public class LogCriteria {
*/
private String message;
/**
* Getter of level.
*
* @return level
*/
public String getLevel() {
return level;
public Level getMinLevel() {
return minLevel;
}
/**
* Setter of level.
*
* @param level level
*/
public void setLevel(String level) {
this.level = StringUtils.lowerCase(level);
public LogCriteria setMinLevel(Level level) {
this.minLevel = level;
return this;
}
/**
* Getter of tag.
*
* @return tag
*/
public String getTag() {
return tag;
}
/**
* Setter of tag.
*
* @param tag tag
*/
public void setTag(String tag) {
public LogCriteria setTag(String tag) {
this.tag = StringUtils.lowerCase(tag);
return this;
}
/**
* Getter of message.
*
* @return message
*/
public String getMessage() {
return message;
}
/**
* Setter of message.
*
* @param message message
*/
public void setMessage(String message) {
public LogCriteria setMessage(String message) {
this.message = StringUtils.lowerCase(message);
return this;
}
}

View File

@ -1,5 +1,7 @@
package com.sismics.util.log4j;
import org.apache.log4j.Level;
/**
* Log entry.
*
@ -14,7 +16,7 @@ public class LogEntry {
/**
* Logging level (DEBUG, WARN)...
*/
private String level;
private Level level;
/**
* Logger name / tag.
@ -34,45 +36,25 @@ public class LogEntry {
* @param tag Logger name / tag
* @param message Message logged
*/
public LogEntry(long timestamp, String level, String tag, String message) {
public LogEntry(long timestamp, Level level, String tag, String message) {
this.timestamp = timestamp;
this.level = level;
this.tag = tag;
this.message = message;
}
/**
* Getter of timestamp.
*
* @return timestamp
*/
public long getTimestamp() {
return timestamp;
}
/**
* Getter of level.
*
* @return level
*/
public String getLevel() {
public Level getLevel() {
return level;
}
/**
* Getter of tag.
*
* @return tag
*/
public String getTag() {
return tag;
}
/**
* Getter of message.
*
* @return message
*/
public String getMessage() {
return message;
}

View File

@ -1,17 +1,19 @@
package com.sismics.util.log4j;
import com.google.common.collect.Lists;
import com.sismics.docs.core.util.jpa.PaginatedList;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Level;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;
import com.google.common.collect.Lists;
import com.sismics.docs.core.util.jpa.PaginatedList;
/**
* Memory appender for Log4J.
*
@ -54,7 +56,7 @@ public class MemoryAppender extends AppenderSkeleton {
String loggerName = getLoggerName(event);
LogEntry logEntry = new LogEntry(System.currentTimeMillis(), event.getLevel().toString(), loggerName, event.getMessage().toString());
LogEntry logEntry = new LogEntry(System.currentTimeMillis(), event.getLevel(), loggerName, event.getMessage().toString());
logQueue.add(logEntry);
}
@ -98,13 +100,13 @@ public class MemoryAppender extends AppenderSkeleton {
*/
public void find(LogCriteria criteria, PaginatedList<LogEntry> list) {
List<LogEntry> logEntryList = new LinkedList<LogEntry>();
final String level = criteria.getLevel();
final Level minLevel = criteria.getMinLevel();
final String tag = criteria.getTag();
final String message = criteria.getMessage();
int resultCount = 0;
for (Iterator<LogEntry> it = logQueue.iterator(); it.hasNext();) {
LogEntry logEntry = it.next();
if ((level == null || logEntry.getLevel().toLowerCase().equals(level)) &&
if ((minLevel == null || Integer.compare(logEntry.getLevel().toInt(), minLevel.toInt()) >= 0) &&
(tag == null || logEntry.getTag().toLowerCase().equals(tag)) &&
(message == null || logEntry.getMessage().toLowerCase().contains(message))) {
logEntryList.add(logEntry);

View File

@ -1 +1 @@
db.version=6
db.version=8

View File

@ -0,0 +1,3 @@
create cached table T_RELATION ( REL_ID_C varchar(36) not null, REL_IDDOCFROM_C varchar(36) not null, REL_IDDOCTO_C varchar(36) not null, REL_DELETEDATE_D datetime, primary key (REL_ID_C) );
update T_CONFIG set CFG_VALUE_C = '7' where CFG_ID_C = 'DB_VERSION';

View File

@ -0,0 +1,7 @@
create memory table T_GROUP ( GRP_ID_C varchar(36) not null, GRP_IDPARENT_C varchar(36), GRP_NAME_C varchar(50) not null, GRP_IDROLE_C varchar(36), GRP_DELETEDATE_D datetime, primary key (GRP_ID_C) );
create memory table T_USER_GROUP ( UGP_ID_C varchar(36) not null, UGP_IDUSER_C varchar(36) not null, UGP_IDGROUP_C varchar(36) not null, UGP_DELETEDATE_D datetime, primary key (UGP_ID_C) );
insert into T_GROUP(GRP_ID_C, GRP_NAME_C, GRP_IDROLE_C) values('administrators', 'administrators', 'admin');
insert into T_USER_GROUP(UGP_ID_C, UGP_IDUSER_C, UGP_IDGROUP_C) values('admin-administrators', 'admin', 'administrators');
update T_CONFIG set CFG_VALUE_C = '8' where CFG_ID_C = 'DB_VERSION';

View File

@ -3,16 +3,18 @@ package com.sismics.docs.core.util;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Date;
import org.junit.Assert;
import org.junit.Test;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import com.google.common.io.Resources;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.util.mime.MimeType;
import org.junit.Assert;
/**
* Test of the file entity utilities.
*
@ -50,6 +52,21 @@ public class TestFileUtil {
InputStream inputStream2 = Resources.getResource("file/udhr_encrypted.pdf").openStream();
InputStream inputStream3 = Resources.getResource("file/document.docx").openStream();
InputStream inputStream4 = Resources.getResource("file/document.odt").openStream()) {
// Document
DocumentDto documentDto = new DocumentDto();
documentDto.setTitle("My super document 1");
documentDto.setDescription("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis id turpis iaculis, commodo est ac, efficitur quam. Nam accumsan magna in orci vulputate ultricies. Sed vulputate neque magna, at laoreet leo ultricies vel. Proin eu hendrerit felis. Quisque sit amet arcu efficitur, pulvinar orci sed, imperdiet elit. Nunc posuere ex sed fermentum congue. Aliquam ultrices convallis finibus. Praesent iaculis justo vitae dictum auctor. Praesent suscipit imperdiet erat ac maximus. Aenean pharetra quam sed fermentum commodo. Donec sagittis ipsum nibh, id congue dolor venenatis quis. In tincidunt nisl non ex sollicitudin, a imperdiet neque scelerisque. Nullam lacinia ac orci sed faucibus. Donec tincidunt venenatis justo, nec fermentum justo rutrum a.");
documentDto.setSubject("A set of random picture");
documentDto.setIdentifier("ID-2016-08-00001");
documentDto.setPublisher("My Publisher, Inc.");
documentDto.setFormat("A4 standard ISO format");
documentDto.setType("Image");
documentDto.setCoverage("France");
documentDto.setRights("Public Domain");
documentDto.setLanguage("en");
documentDto.setCreator("user1");
documentDto.setCreateTimestamp(new Date().getTime());
// First file
Files.copy(inputStream0, DirectoryUtil.getStorageDirectory().resolve("apollo_landscape"), StandardCopyOption.REPLACE_EXISTING);
File file0 = new File();
@ -81,7 +98,9 @@ public class TestFileUtil {
file4.setId("document_odt");
file4.setMimeType(MimeType.OPEN_DOCUMENT_TEXT);
PdfUtil.convertToPdf(Lists.newArrayList(file0, file1, file2, file3, file4), true, 10).close();
try (InputStream pdfInputStream = PdfUtil.convertToPdf(documentDto, Lists.newArrayList(file0, file1, file2, file3, file4), true, true, 10)) {
ByteStreams.copy(pdfInputStream, System.out);
}
}
}
}

View File

@ -26,14 +26,14 @@
<org.slf4j.jcl-over-slf4j.version>1.6.6</org.slf4j.jcl-over-slf4j.version>
<junit.junit.version>4.12</junit.junit.version>
<com.h2database.h2.version>1.4.191</com.h2database.h2.version>
<org.glassfish.jersey.version>2.22.1</org.glassfish.jersey.version>
<org.glassfish.jersey.version>2.22.2</org.glassfish.jersey.version>
<org.mindrot.jbcrypt>0.3m</org.mindrot.jbcrypt>
<org.apache.lucene.version>5.5.0</org.apache.lucene.version>
<org.imgscalr.imgscalr-lib.version>4.2</org.imgscalr.imgscalr-lib.version>
<org.apache.pdfbox.pdfbox.version>2.0.0-RC3</org.apache.pdfbox.pdfbox.version>
<org.bouncycastle.bcprov-jdk15on.version>1.54</org.bouncycastle.bcprov-jdk15on.version>
<joda-time.joda-time.version>2.9.1</joda-time.joda-time.version>
<org.hibernate.hibernate.version>5.0.7.Final</org.hibernate.hibernate.version>
<joda-time.joda-time.version>2.9.2</joda-time.joda-time.version>
<org.hibernate.hibernate.version>5.1.0.Final</org.hibernate.hibernate.version>
<javax.servlet.javax.servlet-api.version>3.1.0</javax.servlet.javax.servlet-api.version>
<fr.opensagres.xdocreport.version>1.0.5</fr.opensagres.xdocreport.version>
<net.java.dev.jna.jna.version>4.2.1</net.java.dev.jna.jna.version>

View File

@ -1,7 +1,11 @@
package com.sismics.security;
import java.util.Set;
import org.joda.time.DateTimeZone;
import jersey.repackaged.com.google.common.collect.Sets;
/**
* Anonymous principal.
*
@ -47,12 +51,12 @@ public class AnonymousPrincipal implements IPrincipal {
return null;
}
/**
* Setter of dateTimeZone.
*
* @param dateTimeZone dateTimeZone
*/
public void setDateTimeZone(DateTimeZone dateTimeZone) {
this.dateTimeZone = dateTimeZone;
}
@Override
public Set<String> getGroupIdSet() {
return Sets.newHashSet();
}
}

View File

@ -1,6 +1,7 @@
package com.sismics.security;
import java.security.Principal;
import java.util.Set;
import org.joda.time.DateTimeZone;
@ -24,6 +25,14 @@ public interface IPrincipal extends Principal {
*/
public String getId();
/**
* Returns the list of group ID of the connected user,
* or an empty list if the user is anonymous.
*
* @return List of group ID
*/
public Set<String> getGroupIdSet();
/**
* Returns the timezone of the principal.
*

View File

@ -35,6 +35,11 @@ public class UserPrincipal implements IPrincipal {
*/
private Set<String> baseFunctionSet;
/**
* User groups.
*/
private Set<String> groupIdSet;
/**
* Constructor of UserPrincipal.
*
@ -56,11 +61,6 @@ public class UserPrincipal implements IPrincipal {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
@ -70,11 +70,6 @@ public class UserPrincipal implements IPrincipal {
return name;
}
/**
* Setter of name.
*
* @param name name
*/
public void setName(String name) {
this.name = name;
}
@ -84,11 +79,6 @@ public class UserPrincipal implements IPrincipal {
return dateTimeZone;
}
/**
* Setter of dateTimeZone.
*
* @param dateTimeZone dateTimeZone
*/
public void setDateTimeZone(DateTimeZone dateTimeZone) {
this.dateTimeZone = dateTimeZone;
}
@ -98,31 +88,24 @@ public class UserPrincipal implements IPrincipal {
return email;
}
/**
* Setter of email.
*
* @param email email
*/
public void setEmail(String email) {
this.email = email;
}
/**
* Getter of baseFunctionSet.
*
* @return baseFunctionSet
*/
public Set<String> getBaseFunctionSet() {
return baseFunctionSet;
}
/**
* Setter of baseFunctionSet.
*
* @param baseFunctionSet baseFunctionSet
*/
public void setBaseFunctionSet(Set<String> baseFunctionSet) {
this.baseFunctionSet = baseFunctionSet;
}
@Override
public Set<String> getGroupIdSet() {
return groupIdSet;
}
public void setGroupIdSet(Set<String> groupIdSet) {
this.groupIdSet = groupIdSet;
}
}

View File

@ -3,6 +3,8 @@ package com.sismics.util.filter;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.Filter;
@ -20,18 +22,22 @@ import org.slf4j.LoggerFactory;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.jpa.AuthenticationTokenDao;
import com.sismics.docs.core.dao.jpa.GroupDao;
import com.sismics.docs.core.dao.jpa.RoleBaseFunctionDao;
import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria;
import com.sismics.docs.core.dao.jpa.dto.GroupDto;
import com.sismics.docs.core.model.jpa.AuthenticationToken;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.TransactionUtil;
import com.sismics.security.AnonymousPrincipal;
import com.sismics.security.UserPrincipal;
import jersey.repackaged.com.google.common.collect.Sets;
/**
* This filter is used to authenticate the user having an active session via an authentication token stored in database.
* The filter extracts the authentication token stored in a cookie.
* If the ocokie exists and the token is valid, the filter injects a UserPrincipal into a request attribute.
* If the cookie exists and the token is valid, the filter injects a UserPrincipal into a request attribute.
* If not, the user is anonymous, and the filter injects a AnonymousPrincipal into the request attribute.
*
* @author jtremeaux
@ -113,10 +119,6 @@ public class TokenBasedSecurityFilter implements Filter {
User user = userDao.getById(authenticationToken.getUserId());
if (user != null && user.getDeleteDate() == null) {
injectAuthenticatedUser(request, user);
// Update the last connection date
authenticationTokenDao.updateLastConnectionDate(authenticationToken.getId());
TransactionUtil.commit();
} else {
injectAnonymousUser(request);
}
@ -153,9 +155,25 @@ public class TokenBasedSecurityFilter implements Filter {
private void injectAuthenticatedUser(HttpServletRequest request, User user) {
UserPrincipal userPrincipal = new UserPrincipal(user.getId(), user.getUsername());
// Add groups
GroupDao groupDao = new GroupDao();
Set<String> groupRoleIdSet = new HashSet<>();
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria()
.setUserId(user.getId())
.setRecursive(true), null);
Set<String> groupIdSet = Sets.newHashSet();
for (GroupDto groupDto : groupDtoList) {
groupIdSet.add(groupDto.getId());
if (groupDto.getRoleId() != null) {
groupRoleIdSet.add(groupDto.getRoleId());
}
}
userPrincipal.setGroupIdSet(groupIdSet);
// Add base functions
groupRoleIdSet.add(user.getRoleId());
RoleBaseFunctionDao userBaseFuction = new RoleBaseFunctionDao();
Set<String> baseFunctionSet = userBaseFuction.findByRoleId(user.getRoleId());
Set<String> baseFunctionSet = userBaseFuction.findByRoleId(groupRoleIdSet);
userPrincipal.setBaseFunctionSet(baseFunctionSet);
// Add email

View File

@ -31,22 +31,59 @@ public class ClientUtil {
*
* @param username Username
*/
public void createUser(String username) {
public void createUser(String username, String... groupNameList) {
// Login admin to create the user
String adminAuthenticationToken = login("admin", "admin", false);
String adminToken = login("admin", "admin", false);
// Create the user
Form form = new Form();
form.param("username", username);
form.param("email", username + "@docs.com");
form.param("password", "12345678");
form.param("storage_quota", "1000000"); // 1MB quota
resource.path("/user").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminAuthenticationToken)
.put(Entity.form(form), JsonObject.class);
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.put(Entity.form(new Form()
.param("username", username)
.param("email", username + "@docs.com")
.param("password", "12345678")
.param("storage_quota", "1000000")), JsonObject.class); // 1MB quota
// Add to groups
for (String groupName : groupNameList) {
resource.path("/group/" + groupName).request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.put(Entity.form(new Form()
.param("username", username)), JsonObject.class);
}
// Logout admin
logout(adminAuthenticationToken);
logout(adminToken);
}
/**
* Creates a group.
*
* @param name Name
*/
public void createGroup(String name) {
createGroup(name, null);
}
/**
* Creates a group.
*
* @param name Name
* @param parent Parent
*/
public void createGroup(String name, String parentId) {
// Login admin to create the group
String adminToken = login("admin", "admin", false);
// Create the gorup
resource.path("/group").request()
.cookie(TokenBasedSecurityFilter.COOKIE_NAME, adminToken)
.put(Entity.form(new Form()
.param("name", name)
.param("parent", parentId)), JsonObject.class);
// Logout admin
logout(adminToken);
}
/**

View File

@ -1,3 +1,3 @@
api.current_version=${project.version}
api.min_version=1.0
db.version=6
db.version=8

View File

@ -1,6 +1,7 @@
package com.sismics.docs.rest.resource;
import java.text.MessageFormat;
import java.util.List;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
@ -14,18 +15,21 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import com.google.common.collect.Lists;
import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.GroupDao;
import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria;
import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
import com.sismics.docs.core.dao.jpa.dto.GroupDto;
import com.sismics.docs.core.dao.jpa.dto.UserDto;
import com.sismics.docs.core.model.jpa.Acl;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.Group;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
@ -41,12 +45,17 @@ public class AclResource extends BaseResource {
/**
* Add an ACL.
*
* @param sourceId Source ID
* @param permStr Permission
* @param targetName Target name
* @param type ACL type
* @return Response
*/
@PUT
public Response add(@FormParam("source") String sourceId,
@FormParam("perm") String permStr,
@FormParam("username") String username) {
@FormParam("target") String targetName,
@FormParam("type") String typeStr) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
@ -54,18 +63,39 @@ public class AclResource extends BaseResource {
// Validate input
ValidationUtil.validateRequired(sourceId, "source");
PermType perm = PermType.valueOf(ValidationUtil.validateLength(permStr, "perm", 1, 30, false));
username = ValidationUtil.validateLength(username, "username", 1, 50, false);
AclTargetType type = AclTargetType.valueOf(ValidationUtil.validateLength(typeStr, "type", 1, 10, false));
targetName = ValidationUtil.validateLength(targetName, "target", 1, 50, false);
// Validate the target user
// Search user or group
String targetId = null;
switch (type) {
case USER:
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
throw new ClientException("UserNotFound", MessageFormat.format("User not found: {0}", username));
User user = userDao.getActiveByUsername(targetName);
if (user != null) {
targetId = user.getId();
}
break;
case GROUP:
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(targetName);
if (group != null) {
targetId = group.getId();
}
break;
case SHARE:
// Share must use the Share REST resource
break;
}
// Does a target has been found?
if (targetId == null) {
throw new ClientException("InvalidTarget", MessageFormat.format("This target does not exist: {0}", targetName));
}
// Check permission on the source by the principal
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(sourceId, PermType.WRITE, principal.getId())) {
if (!aclDao.checkPermission(sourceId, PermType.WRITE, getTargetIdList(null))) {
throw new ForbiddenClientException();
}
@ -73,18 +103,18 @@ public class AclResource extends BaseResource {
Acl acl = new Acl();
acl.setSourceId(sourceId);
acl.setPerm(perm);
acl.setTargetId(user.getId());
acl.setTargetId(targetId);
// Avoid duplicates
if (!aclDao.checkPermission(acl.getSourceId(), acl.getPerm(), acl.getTargetId())) {
if (!aclDao.checkPermission(acl.getSourceId(), acl.getPerm(), Lists.newArrayList(acl.getTargetId()))) {
aclDao.create(acl, principal.getId());
// Returns the ACL
JsonObjectBuilder response = Json.createObjectBuilder()
.add("perm", acl.getPerm().name())
.add("id", acl.getTargetId())
.add("name", user.getUsername())
.add("type", AclTargetType.USER.name());
.add("name", targetName)
.add("type", type.name());
return Response.ok().entity(response.build()).build();
}
@ -94,7 +124,9 @@ public class AclResource extends BaseResource {
/**
* Deletes an ACL.
*
* @param id ACL ID
* @param sourceId Source ID
* @param permStr Permission
* @param targetId Target ID
* @return Response
*/
@DELETE
@ -114,7 +146,7 @@ public class AclResource extends BaseResource {
// Check permission on the source by the principal
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(sourceId, PermType.WRITE, principal.getId())) {
if (!aclDao.checkPermission(sourceId, PermType.WRITE, getTargetIdList(null))) {
throw new ForbiddenClientException();
}
@ -153,18 +185,25 @@ public class AclResource extends BaseResource {
// Search users
UserDao userDao = new UserDao();
JsonArrayBuilder users = Json.createArrayBuilder();
PaginatedList<UserDto> paginatedList = PaginatedLists.create();
SortCriteria sortCriteria = new SortCriteria(1, true);
userDao.findByCriteria(paginatedList, new UserCriteria().setSearch(search), sortCriteria);
for (UserDto userDto : paginatedList.getResultList()) {
List<UserDto> userDtoList = userDao.findByCriteria(new UserCriteria().setSearch(search), sortCriteria);
for (UserDto userDto : userDtoList) {
users.add(Json.createObjectBuilder()
.add("username", userDto.getUsername()));
.add("name", userDto.getUsername()));
}
// Search groups
GroupDao groupDao = new GroupDao();
JsonArrayBuilder groups = Json.createArrayBuilder();
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria().setSearch(search), sortCriteria);
for (GroupDto groupDto : groupDtoList) {
groups.add(Json.createObjectBuilder()
.add("name", groupDto.getName()));
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("users", users);
.add("users", users)
.add("groups", groups);
return Response.ok().entity(response.build()).build();
}
}

View File

@ -22,6 +22,7 @@ import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Appender;
import org.apache.log4j.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -81,7 +82,7 @@ public class AppResource extends BaseResource {
/**
* Retrieve the application logs.
*
* @param level Filter on logging level
* @param minLevel Filter on logging level
* @param tag Filter on logger name / tag
* @param message Filter on message
* @param limit Page limit
@ -91,7 +92,7 @@ public class AppResource extends BaseResource {
@GET
@Path("log")
public Response log(
@QueryParam("level") String level,
@QueryParam("level") String minLevel,
@QueryParam("tag") String tag,
@QueryParam("message") String message,
@QueryParam("limit") Integer limit,
@ -99,8 +100,6 @@ public class AppResource extends BaseResource {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// TODO Change level by minLevel (returns all logs above)
// Get the memory appender
org.apache.log4j.Logger logger = org.apache.log4j.Logger.getRootLogger();
Appender appender = logger.getAppender("MEMORY");
@ -110,10 +109,10 @@ public class AppResource extends BaseResource {
MemoryAppender memoryAppender = (MemoryAppender) appender;
// Find the logs
LogCriteria logCriteria = new LogCriteria();
logCriteria.setLevel(StringUtils.stripToNull(level));
logCriteria.setTag(StringUtils.stripToNull(tag));
logCriteria.setMessage(StringUtils.stripToNull(message));
LogCriteria logCriteria = new LogCriteria()
.setMinLevel(Level.toLevel(StringUtils.stripToNull(minLevel)))
.setTag(StringUtils.stripToNull(tag))
.setMessage(StringUtils.stripToNull(message));
PaginatedList<LogEntry> paginatedList = PaginatedLists.create(limit, offset);
memoryAppender.find(logCriteria, paginatedList);
@ -121,7 +120,7 @@ public class AppResource extends BaseResource {
for (LogEntry logEntry : paginatedList.getResultList()) {
logs.add(Json.createObjectBuilder()
.add("date", logEntry.getTimestamp())
.add("level", logEntry.getLevel())
.add("level", logEntry.getLevel().toString())
.add("tag", logEntry.getTag())
.add("message", logEntry.getMessage()));
}

View File

@ -9,6 +9,7 @@ import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.google.common.base.Strings;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.docs.core.dao.jpa.AuditLogDao;
@ -18,7 +19,6 @@ import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.rest.util.JsonUtil;
/**
@ -43,25 +43,21 @@ public class AuditLogResource extends BaseResource {
PaginatedList<AuditLogDto> paginatedList = PaginatedLists.create(20, 0);
SortCriteria sortCriteria = new SortCriteria(1, false);
AuditLogCriteria criteria = new AuditLogCriteria();
if (documentId == null) {
if (Strings.isNullOrEmpty(documentId)) {
// Search logs for a user
criteria.setUserId(principal.getId());
} else {
// Check ACL on the document
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(documentId, PermType.READ, principal.getId())) {
if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(null))) {
return Response.status(Status.NOT_FOUND).build();
}
criteria.setDocumentId(documentId);
}
// Search the logs
try {
AuditLogDao auditLogDao = new AuditLogDao();
auditLogDao.findByCriteria(paginatedList, criteria, sortCriteria);
} catch (Exception e) {
throw new ServerException("SearchError", "Error searching in logs", e);
}
// Assemble the results
JsonArrayBuilder logs = Json.createArrayBuilder();

View File

@ -1,12 +1,14 @@
package com.sismics.docs.rest.resource;
import java.security.Principal;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import com.google.common.collect.Lists;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.security.IPrincipal;
@ -77,4 +79,21 @@ public abstract class BaseResource {
Set<String> baseFunctionSet = ((UserPrincipal) principal).getBaseFunctionSet();
return baseFunctionSet != null && baseFunctionSet.contains(baseFunction.name());
}
/**
* Returns a list of ACL target ID.
*
* @param shareId Share ID (optional)
* @return List of ACL target ID
*/
protected List<String> getTargetIdList(String shareId) {
List<String> targetIdList = Lists.newArrayList(principal.getGroupIdSet());
if (principal.getId() != null) {
targetIdList.add(principal.getId());
}
if (shareId != null) {
targetIdList.add(shareId);
}
return targetIdList;
}
}

View File

@ -51,7 +51,7 @@ public class CommentResource extends BaseResource {
// Read access on doc gives access to write comments
DocumentDao documentDao = new DocumentDao();
if (documentDao.getDocument(documentId, PermType.READ, principal.getId()) == null) {
if (documentDao.getDocument(documentId, PermType.READ, getTargetIdList(null)) == null) {
return Response.status(Status.NOT_FOUND).build();
}
@ -97,7 +97,7 @@ public class CommentResource extends BaseResource {
if (!comment.getUserId().equals(principal.getId())) {
// Get the associated document
DocumentDao documentDao = new DocumentDao();
if (documentDao.getDocument(comment.getDocumentId(), PermType.WRITE, principal.getId()) == null) {
if (documentDao.getDocument(comment.getDocumentId(), PermType.WRITE, getTargetIdList(null)) == null) {
return Response.status(Status.NOT_FOUND).build();
}
}
@ -125,7 +125,7 @@ public class CommentResource extends BaseResource {
// Read access on doc gives access to read comments
DocumentDao documentDao = new DocumentDao();
if (documentDao.getDocument(documentId, PermType.READ, shareId == null ? principal.getId() : shareId) == null) {
if (documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId)) == null) {
return Response.status(Status.NOT_FOUND).build();
}

View File

@ -27,7 +27,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.StreamingOutput;
import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
@ -43,12 +42,14 @@ import com.sismics.docs.core.dao.jpa.AclDao;
import com.sismics.docs.core.dao.jpa.ContributorDao;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.FileDao;
import com.sismics.docs.core.dao.jpa.RelationDao;
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.ContributorDto;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.dao.jpa.dto.RelationDto;
import com.sismics.docs.core.dao.jpa.dto.TagDto;
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
@ -93,16 +94,11 @@ public class DocumentResource extends BaseResource {
DocumentDao documentDao = new DocumentDao();
AclDao aclDao = new AclDao();
DocumentDto documentDto = documentDao.getDocument(documentId);
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId));
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
}
// Check document visibility
if (!aclDao.checkPermission(documentId, PermType.READ, shareId == null ? principal.getId() : shareId)) {
throw new ForbiddenClientException();
}
JsonObjectBuilder document = Json.createObjectBuilder()
.add("id", documentDto.getId())
.add("title", documentDto.getTitle())
@ -152,7 +148,8 @@ public class DocumentResource extends BaseResource {
.add("type", aclDto.getTargetType()));
if (!principal.isAnonymous()
&& aclDto.getTargetId().equals(principal.getId())
&& (aclDto.getTargetId().equals(principal.getId())
|| principal.getGroupIdSet().contains(aclDto.getTargetId()))
&& aclDto.getPerm() == PermType.WRITE) {
// The document is writable for the current user
writable = true;
@ -172,6 +169,18 @@ public class DocumentResource extends BaseResource {
}
document.add("contributors", contributorList);
// Add relations
RelationDao relationDao = new RelationDao();
List<RelationDto> relationDtoList = relationDao.getByDocumentId(documentId);
JsonArrayBuilder relationList = Json.createArrayBuilder();
for (RelationDto relationDto : relationDtoList) {
relationList.add(Json.createObjectBuilder()
.add("id", relationDto.getId())
.add("title", relationDto.getTitle())
.add("source", relationDto.isSource()));
}
document.add("relations", relationList);
return Response.ok().entity(document.build()).build();
}
@ -186,8 +195,8 @@ public class DocumentResource extends BaseResource {
public Response getPdf(
@PathParam("id") String documentId,
@QueryParam("share") String shareId,
@QueryParam("metadata") Boolean metadata,
@QueryParam("comments") Boolean comments,
final @QueryParam("metadata") Boolean metadata,
final @QueryParam("comments") Boolean comments,
final @QueryParam("fitimagetopage") Boolean fitImageToPage,
@QueryParam("margin") String marginStr) {
authenticate();
@ -197,8 +206,8 @@ public class DocumentResource extends BaseResource {
// Get document and check read permission
DocumentDao documentDao = new DocumentDao();
Document document = documentDao.getDocument(documentId, PermType.READ, shareId == null ? principal.getId() : shareId);
if (document == null) {
final DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId));
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
}
@ -217,7 +226,7 @@ public class DocumentResource extends BaseResource {
StreamingOutput stream = new StreamingOutput() {
@Override
public void write(OutputStream outputStream) throws IOException, WebApplicationException {
try (InputStream inputStream = PdfUtil.convertToPdf(fileList, fitImageToPage, margin)) {
try (InputStream inputStream = PdfUtil.convertToPdf(documentDto, fileList, fitImageToPage, metadata, margin)) {
ByteStreams.copy(inputStream, outputStream);
} catch (Exception e) {
throw new IOException(e);
@ -229,7 +238,7 @@ public class DocumentResource extends BaseResource {
return Response.ok(stream)
.header("Content-Type", MimeType.APPLICATION_PDF)
.header("Content-Disposition", "inline; filename=\"" + document.getTitle() + ".pdf\"")
.header("Content-Disposition", "inline; filename=\"" + documentDto.getTitle() + ".pdf\"")
.build();
}
@ -260,7 +269,7 @@ public class DocumentResource extends BaseResource {
PaginatedList<DocumentDto> paginatedList = PaginatedLists.create(limit, offset);
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
DocumentCriteria documentCriteria = parseSearchQuery(search);
documentCriteria.setUserId(principal.getId());
documentCriteria.setTargetIdList(getTargetIdList(null));
try {
documentDao.findByCriteria(paginatedList, documentCriteria, sortCriteria);
} catch (Exception e) {
@ -430,6 +439,7 @@ public class DocumentResource extends BaseResource {
@FormParam("coverage") String coverage,
@FormParam("rights") String rights,
@FormParam("tags") List<String> tagList,
@FormParam("relations") List<String> relationList,
@FormParam("language") String language,
@FormParam("create_date") String createDateStr) {
if (!authenticate()) {
@ -493,6 +503,9 @@ public class DocumentResource extends BaseResource {
// Update tags
updateTagList(documentId, tagList);
// Update relations
updateRelationList(documentId, relationList);
// Raise a document created event
DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent();
documentCreatedAsyncEvent.setUserId(principal.getId());
@ -526,6 +539,7 @@ public class DocumentResource extends BaseResource {
@FormParam("coverage") String coverage,
@FormParam("rights") String rights,
@FormParam("tags") List<String> tagList,
@FormParam("relations") List<String> relationList,
@FormParam("language") String language,
@FormParam("create_date") String createDateStr) {
if (!authenticate()) {
@ -533,8 +547,8 @@ public class DocumentResource extends BaseResource {
}
// Validate input data
title = ValidationUtil.validateLength(title, "title", 1, 100, true);
language = ValidationUtil.validateLength(language, "language", 3, 3, true);
title = ValidationUtil.validateLength(title, "title", 1, 100, false);
language = ValidationUtil.validateLength(language, "language", 3, 3, false);
description = ValidationUtil.validateLength(description, "description", 0, 4000, true);
subject = ValidationUtil.validateLength(subject, "subject", 0, 500, true);
identifier = ValidationUtil.validateLength(identifier, "identifier", 0, 500, true);
@ -549,50 +563,35 @@ public class DocumentResource extends BaseResource {
throw new ClientException("ValidationError", MessageFormat.format("{0} is not a supported language", language));
}
// Check write permission
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(id, PermType.WRITE, getTargetIdList(null))) {
throw new ForbiddenClientException();
}
// Get the document
DocumentDao documentDao = new DocumentDao();
Document document;
document = documentDao.getDocument(id, PermType.WRITE, principal.getId());
Document document = documentDao.getById(id);
if (document == null) {
return Response.status(Status.NOT_FOUND).build();
}
// Update the document
if (!StringUtils.isEmpty(title)) {
document.setTitle(title);
}
if (!StringUtils.isEmpty(description)) {
document.setDescription(description);
}
if (!StringUtils.isEmpty(subject)) {
document.setSubject(subject);
}
if (!StringUtils.isEmpty(identifier)) {
document.setIdentifier(identifier);
}
if (!StringUtils.isEmpty(publisher)) {
document.setPublisher(publisher);
}
if (!StringUtils.isEmpty(format)) {
document.setFormat(format);
}
if (!StringUtils.isEmpty(source)) {
document.setSource(source);
}
if (!StringUtils.isEmpty(type)) {
document.setType(type);
}
if (!StringUtils.isEmpty(coverage)) {
document.setCoverage(coverage);
}
if (!StringUtils.isEmpty(rights)) {
document.setRights(rights);
}
if (createDate != null) {
document.setCreateDate(createDate);
}
if (language != null) {
document.setLanguage(language);
if (createDate == null) {
document.setCreateDate(new Date());
} else {
document.setCreateDate(createDate);
}
document = documentDao.update(document, principal.getId());
@ -600,10 +599,13 @@ public class DocumentResource extends BaseResource {
// Update tags
updateTagList(id, tagList);
// Raise a document updated event
// Update relations
updateRelationList(id, relationList);
// Raise a document updated event (with the document to update Lucene)
DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent();
documentUpdatedAsyncEvent.setUserId(principal.getId());
documentUpdatedAsyncEvent.setDocument(document);
documentUpdatedAsyncEvent.setDocumentId(id);
AppContext.getInstance().getAsyncEventBus().post(documentUpdatedAsyncEvent);
JsonObjectBuilder response = Json.createObjectBuilder()
@ -636,6 +638,28 @@ public class DocumentResource extends BaseResource {
}
}
/**
* Update relations list on a document.
*
* @param documentId Document ID
* @param relationList Relation ID list
*/
private void updateRelationList(String documentId, List<String> relationList) {
if (relationList != null) {
DocumentDao documentDao = new DocumentDao();
RelationDao relationDao = new RelationDao();
Set<String> documentIdSet = new HashSet<>();
for (String targetDocId : relationList) {
// ACL are not checked, because the editing user is not forced to view the target document
Document document = documentDao.getById(targetDocId);
if (document != null && !documentId.equals(targetDocId)) {
documentIdSet.add(targetDocId);
}
}
relationDao.updateRelationList(documentId, documentIdSet);
}
}
/**
* Deletes a document.
*
@ -653,14 +677,14 @@ public class DocumentResource extends BaseResource {
// Get the document
DocumentDao documentDao = new DocumentDao();
FileDao fileDao = new FileDao();
Document document = documentDao.getDocument(id, PermType.WRITE, principal.getId());
List<File> fileList = fileDao.getByDocumentId(principal.getId(), id);
if (document == null) {
DocumentDto documentDto = documentDao.getDocument(id, PermType.WRITE, getTargetIdList(null));
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
}
List<File> fileList = fileDao.getByDocumentId(principal.getId(), id);
// Delete the document
documentDao.delete(document.getId(), principal.getId());
documentDao.delete(documentDto.getId(), principal.getId());
// Raise file deleted events (don't bother sending document updated event)
for (File file : fileList) {
@ -673,7 +697,7 @@ public class DocumentResource extends BaseResource {
// Raise a document deleted event
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
documentDeletedAsyncEvent.setUserId(principal.getId());
documentDeletedAsyncEvent.setDocument(document);
documentDeletedAsyncEvent.setDocumentId(documentDto.getId());
AppContext.getInstance().getAsyncEventBus().post(documentDeletedAsyncEvent);
// Always return OK

View File

@ -48,7 +48,6 @@ import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
import com.sismics.docs.core.event.FileCreatedAsyncEvent;
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.DirectoryUtil;
@ -94,13 +93,13 @@ public class FileResource extends BaseResource {
User user = userDao.getById(principal.getId());
// Get the document
Document document = null;
DocumentDto documentDto = null;
if (Strings.isNullOrEmpty(documentId)) {
documentId = null;
} else {
DocumentDao documentDao = new DocumentDao();
document = documentDao.getDocument(documentId, PermType.WRITE, principal.getId());
if (document == null) {
documentDto = documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null));
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
}
}
@ -165,7 +164,7 @@ public class FileResource extends BaseResource {
if (documentId != null) {
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
fileCreatedAsyncEvent.setUserId(principal.getId());
fileCreatedAsyncEvent.setDocument(document);
fileCreatedAsyncEvent.setLanguage(documentDto.getLanguage());
fileCreatedAsyncEvent.setFile(file);
fileCreatedAsyncEvent.setInputStream(fileInputStream);
fileCreatedAsyncEvent.setPdfInputStream(pdfIntputStream);
@ -173,7 +172,7 @@ public class FileResource extends BaseResource {
DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent();
documentUpdatedAsyncEvent.setUserId(principal.getId());
documentUpdatedAsyncEvent.setDocument(document);
documentUpdatedAsyncEvent.setDocumentId(documentId);
AppContext.getInstance().getAsyncEventBus().post(documentUpdatedAsyncEvent);
}
@ -214,8 +213,8 @@ public class FileResource extends BaseResource {
DocumentDao documentDao = new DocumentDao();
FileDao fileDao = new FileDao();
File file = fileDao.getFile(id, principal.getId());
Document document = documentDao.getDocument(documentId, PermType.WRITE, principal.getId());
if (file == null || document == null) {
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null));
if (file == null || documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
}
@ -236,14 +235,14 @@ public class FileResource extends BaseResource {
final InputStream responseInputStream = EncryptionUtil.decryptInputStream(fileInputStream, user.getPrivateKey());
FileCreatedAsyncEvent fileCreatedAsyncEvent = new FileCreatedAsyncEvent();
fileCreatedAsyncEvent.setUserId(principal.getId());
fileCreatedAsyncEvent.setDocument(document);
fileCreatedAsyncEvent.setLanguage(documentDto.getLanguage());
fileCreatedAsyncEvent.setFile(file);
fileCreatedAsyncEvent.setInputStream(responseInputStream);
AppContext.getInstance().getAsyncEventBus().post(fileCreatedAsyncEvent);
DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent();
documentUpdatedAsyncEvent.setUserId(principal.getId());
documentUpdatedAsyncEvent.setDocument(document);
documentUpdatedAsyncEvent.setDocumentId(documentId);
AppContext.getInstance().getAsyncEventBus().post(documentUpdatedAsyncEvent);
} catch (Exception e) {
throw new ClientException("AttachError", "Error attaching file to document", e);
@ -277,7 +276,7 @@ public class FileResource extends BaseResource {
// Get the document
DocumentDao documentDao = new DocumentDao();
if (documentDao.getDocument(documentId, PermType.WRITE, principal.getId()) == null) {
if (documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null)) == null) {
return Response.status(Status.NOT_FOUND).build();
}
@ -313,7 +312,7 @@ public class FileResource extends BaseResource {
// Check document visibility
if (documentId != null) {
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(documentId, PermType.READ, shareId == null ? principal.getId() : shareId)) {
if (!aclDao.checkPermission(documentId, PermType.READ, getTargetIdList(shareId))) {
return Response.status(Status.NOT_FOUND).build();
}
} else if (!authenticated) {
@ -364,14 +363,14 @@ public class FileResource extends BaseResource {
return Response.status(Status.NOT_FOUND).build();
}
Document document = null;
DocumentDto documentDto = null;
if (file.getDocumentId() == null) {
// It's an orphan file
if (!file.getUserId().equals(principal.getId())) {
// But not ours
throw new ForbiddenClientException();
}
} else if ((document = documentDao.getDocument(file.getDocumentId(), PermType.WRITE, principal.getId())) == null) {
} else if ((documentDto = documentDao.getDocument(file.getDocumentId(), PermType.WRITE, getTargetIdList(null))) == null) {
return Response.status(Status.NOT_FOUND).build();
}
@ -395,11 +394,11 @@ public class FileResource extends BaseResource {
fileDeletedAsyncEvent.setFile(file);
AppContext.getInstance().getAsyncEventBus().post(fileDeletedAsyncEvent);
if (document != null) {
if (documentDto != null) {
// Raise a new document updated
DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent();
documentUpdatedAsyncEvent.setUserId(principal.getId());
documentUpdatedAsyncEvent.setDocument(document);
documentUpdatedAsyncEvent.setDocumentId(documentDto.getId());
AppContext.getInstance().getAsyncEventBus().post(documentUpdatedAsyncEvent);
}
@ -446,7 +445,7 @@ public class FileResource extends BaseResource {
} else {
// Check document accessibility
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(file.getDocumentId(), PermType.READ, shareId == null ? principal.getId() : shareId)) {
if (!aclDao.checkPermission(file.getDocumentId(), PermType.READ, getTargetIdList(shareId))) {
throw new ForbiddenClientException();
}
}
@ -520,17 +519,11 @@ public class FileResource extends BaseResource {
// Get the document
DocumentDao documentDao = new DocumentDao();
DocumentDto documentDto = documentDao.getDocument(documentId);
DocumentDto documentDto = documentDao.getDocument(documentId, PermType.READ, getTargetIdList(shareId));
if (documentDto == null) {
return Response.status(Status.NOT_FOUND).build();
}
// Check document visibility
AclDao aclDao = new AclDao();
if (!aclDao.checkPermission(documentId, PermType.READ, shareId == null ? principal.getId() : shareId)) {
throw new ForbiddenClientException();
}
// Get files and user associated with this document
FileDao fileDao = new FileDao();
final UserDao userDao = new UserDao();

View File

@ -0,0 +1,342 @@
package com.sismics.docs.rest.resource;
import java.text.MessageFormat;
import java.util.List;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.google.common.base.Strings;
import com.sismics.docs.core.dao.jpa.GroupDao;
import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria;
import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
import com.sismics.docs.core.dao.jpa.dto.GroupDto;
import com.sismics.docs.core.dao.jpa.dto.UserDto;
import com.sismics.docs.core.model.jpa.Group;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.model.jpa.UserGroup;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.JsonUtil;
import com.sismics.rest.util.ValidationUtil;
/**
* Group REST resources.
*
* @author bgamard
*/
@Path("/group")
public class GroupResource extends BaseResource {
/**
* Add a group.
*
* @return Response
*/
@PUT
public Response add(@FormParam("parent") String parentName,
@FormParam("name") String name) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Validate input
name = ValidationUtil.validateLength(name, "name", 1, 50, false);
ValidationUtil.validateAlphanumeric(name, "name");
// Avoid duplicates
GroupDao groupDao = new GroupDao();
Group existingGroup = groupDao.getActiveByName(name);
if (existingGroup != null) {
throw new ClientException("GroupAlreadyExists", MessageFormat.format("This group already exists: {0}", name));
}
// Validate parent
String parentId = null;
if (!Strings.isNullOrEmpty(parentName)) {
Group parentGroup = groupDao.getActiveByName(parentName);
if (parentGroup == null) {
throw new ClientException("ParentGroupNotFound", MessageFormat.format("This group does not exists: {0}", parentName));
}
parentId = parentGroup.getId();
}
// Create the group
groupDao.create(new Group()
.setName(name)
.setParentId(parentId), principal.getId());
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Update a group.
*
* @return Response
*/
@POST
@Path("{groupName: [a-zA-Z0-9_]+}")
public Response update(@PathParam("groupName") String groupName,
@FormParam("parent") String parentName,
@FormParam("name") String name) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Validate input
name = ValidationUtil.validateLength(name, "name", 1, 50, false);
ValidationUtil.validateAlphanumeric(name, "name");
// Get the group (by its old name)
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group == null) {
return Response.status(Status.NOT_FOUND).build();
}
// Avoid duplicates
Group existingGroup = groupDao.getActiveByName(name);
if (existingGroup != null && existingGroup.getId() != group.getId()) {
throw new ClientException("GroupAlreadyExists", MessageFormat.format("This group already exists: {0}", name));
}
// Validate parent
String parentId = null;
if (!Strings.isNullOrEmpty(parentName)) {
Group parentGroup = groupDao.getActiveByName(parentName);
if (parentGroup == null) {
throw new ClientException("ParentGroupNotFound", MessageFormat.format("This group does not exists: {0}", parentName));
}
parentId = parentGroup.getId();
}
// Update the group
groupDao.update(group.setName(name)
.setParentId(parentId), principal.getId());
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Delete a group.
*
* @return Response
*/
@DELETE
@Path("{groupName: [a-zA-Z0-9_]+}")
public Response delete(@PathParam("groupName") String groupName) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Get the group
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group == null) {
return Response.status(Status.NOT_FOUND).build();
}
// Delete the group
groupDao.delete(group.getId(), principal.getId());
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Add a user to a group.
*
* @param groupName Group name
* @param username Username
* @return Response
*/
@PUT
@Path("{groupName: [a-zA-Z0-9_]+}")
public Response addMember(@PathParam("groupName") String groupName,
@FormParam("username") String username) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Validate input
groupName = ValidationUtil.validateLength(groupName, "name", 1, 50, false);
username = ValidationUtil.validateLength(username, "username", 1, 50, false);
// Get the group
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group == null) {
return Response.status(Status.NOT_FOUND).build();
}
// Get the user
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
return Response.status(Status.NOT_FOUND).build();
}
// Avoid duplicates
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria().setUserId(user.getId()), null);
boolean found = false;
for (GroupDto groupDto : groupDtoList) {
if (groupDto.getId().equals(group.getId())) {
found = true;
}
}
if (!found) {
// Add the membership
UserGroup userGroup = new UserGroup();
userGroup.setGroupId(group.getId());
userGroup.setUserId(user.getId());
groupDao.addMember(userGroup);
}
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Remove an user from a group.
*
* @param groupName Group name
* @param username Username
* @return Response
*/
@DELETE
@Path("{groupName: [a-zA-Z0-9_]+}/{username: [a-zA-Z0-9_]+}")
public Response removeMember(@PathParam("groupName") String groupName,
@PathParam("username") String username) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Validate input
groupName = ValidationUtil.validateLength(groupName, "name", 1, 50, false);
username = ValidationUtil.validateLength(username, "username", 1, 50, false);
// Get the group
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group == null) {
return Response.status(Status.NOT_FOUND).build();
}
// Get the user
UserDao userDao = new UserDao();
User user = userDao.getActiveByUsername(username);
if (user == null) {
return Response.status(Status.NOT_FOUND).build();
}
// Remove the membership
groupDao.removeMember(group.getId(), user.getId());
// Always return OK
JsonObjectBuilder response = Json.createObjectBuilder()
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Returns all active groups.
*
* @param sortColumn Sort index
* @param asc If true, ascending sorting, else descending
* @return Response
*/
@GET
public Response list(
@QueryParam("sort_column") Integer sortColumn,
@QueryParam("asc") Boolean asc) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
JsonArrayBuilder groups = Json.createArrayBuilder();
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
GroupDao groupDao = new GroupDao();
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria(), sortCriteria);
for (GroupDto groupDto : groupDtoList) {
groups.add(Json.createObjectBuilder()
.add("name", groupDto.getName())
.add("parent", JsonUtil.nullable(groupDto.getParentName())));
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("groups", groups);
return Response.ok().entity(response.build()).build();
}
/**
* Get a group.
*
* @param groupName Group name
* @return Response
*/
@GET
@Path("{groupName: [a-zA-Z0-9_]+}")
public Response get(@PathParam("groupName") String groupName) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the group
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group == null) {
return Response.status(Status.NOT_FOUND).build();
}
// Build the response
JsonObjectBuilder response = Json.createObjectBuilder()
.add("name", group.getName());
// Get the parent
if (group.getParentId() != null) {
Group parentGroup = groupDao.getActiveById(group.getParentId());
response.add("parent", parentGroup.getName());
}
// Members
JsonArrayBuilder members = Json.createArrayBuilder();
UserDao userDao = new UserDao();
List<UserDto> userDtoList = userDao.findByCriteria(new UserCriteria().setGroupId(group.getId()), new SortCriteria(1, true));
for (UserDto userDto : userDtoList) {
members.add(userDto.getUsername());
}
response.add("members", members);
return Response.ok().entity(response.build()).build();
}
}

View File

@ -37,6 +37,7 @@ public class ShareResource extends BaseResource {
* Add a share to a document.
*
* @param documentId Document ID
* @param name Share name
* @return Response
*/
@PUT
@ -53,7 +54,7 @@ public class ShareResource extends BaseResource {
// Get the document
DocumentDao documentDao = new DocumentDao();
if (documentDao.getDocument(documentId, PermType.WRITE, principal.getId()) == null) {
if (documentDao.getDocument(documentId, PermType.WRITE, getTargetIdList(null)) == null) {
return Response.status(Status.NOT_FOUND).build();
}
@ -102,7 +103,7 @@ public class ShareResource extends BaseResource {
}
Acl acl = aclList.get(0);
if (!aclDao.checkPermission(acl.getSourceId(), PermType.WRITE, principal.getId())) {
if (!aclDao.checkPermission(acl.getSourceId(), PermType.WRITE, getTargetIdList(null))) {
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", acl.getSourceId()));
}

View File

@ -25,13 +25,17 @@ import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.jpa.AuthenticationTokenDao;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.FileDao;
import com.sismics.docs.core.dao.jpa.GroupDao;
import com.sismics.docs.core.dao.jpa.RoleBaseFunctionDao;
import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.dao.jpa.criteria.GroupCriteria;
import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
import com.sismics.docs.core.dao.jpa.dto.GroupDto;
import com.sismics.docs.core.dao.jpa.dto.UserDto;
import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
@ -39,10 +43,9 @@ import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.AuthenticationToken;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.model.jpa.Group;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.EncryptionUtil;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ClientException;
@ -299,14 +302,7 @@ public class UserResource extends BaseResource {
}
// Get the value of the session token
String authToken = null;
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (TokenBasedSecurityFilter.COOKIE_NAME.equals(cookie.getName())) {
authToken = cookie.getValue();
}
}
}
String authToken = getAuthToken();
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
AuthenticationToken authenticationToken = null;
@ -328,7 +324,7 @@ public class UserResource extends BaseResource {
// Deletes the client token in the HTTP response
JsonObjectBuilder response = Json.createObjectBuilder();
NewCookie cookie = new NewCookie(TokenBasedSecurityFilter.COOKIE_NAME, null);
NewCookie cookie = new NewCookie(TokenBasedSecurityFilter.COOKIE_NAME, null, "/", null, 1, null, -1, new Date(1), false, false);
return Response.ok().entity(response.build()).cookie(cookie).build();
}
@ -362,7 +358,7 @@ public class UserResource extends BaseResource {
for (Document document : documentList) {
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
documentDeletedAsyncEvent.setUserId(principal.getId());
documentDeletedAsyncEvent.setDocument(document);
documentDeletedAsyncEvent.setDocumentId(document.getId());
AppContext.getInstance().getAsyncEventBus().post(documentDeletedAsyncEvent);
}
@ -403,7 +399,7 @@ public class UserResource extends BaseResource {
// Ensure that the admin user is not deleted
RoleBaseFunctionDao userBaseFuction = new RoleBaseFunctionDao();
Set<String> baseFunctionSet = userBaseFuction.findByRoleId(user.getRoleId());
Set<String> baseFunctionSet = userBaseFuction.findByRoleId(Sets.newHashSet(user.getRoleId()));
if (baseFunctionSet.contains(BaseFunction.ADMIN.name())) {
throw new ClientException("ForbiddenError", "The admin user cannot be deleted");
}
@ -421,7 +417,7 @@ public class UserResource extends BaseResource {
for (Document document : documentList) {
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
documentDeletedAsyncEvent.setUserId(principal.getId());
documentDeletedAsyncEvent.setDocument(document);
documentDeletedAsyncEvent.setDocumentId(document.getId());
AppContext.getInstance().getAsyncEventBus().post(documentDeletedAsyncEvent);
}
@ -457,18 +453,39 @@ public class UserResource extends BaseResource {
response.add("is_default_password", Constants.DEFAULT_ADMIN_PASSWORD.equals(adminUser.getPassword()));
}
} else {
// Update the last connection date
String authToken = getAuthToken();
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
authenticationTokenDao.updateLastConnectionDate(authToken);
// Build the response
response.add("anonymous", false);
UserDao userDao = new UserDao();
GroupDao groupDao = new GroupDao();
User user = userDao.getById(principal.getId());
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria()
.setUserId(user.getId())
.setRecursive(true), null);
response.add("username", user.getUsername())
.add("email", user.getEmail())
.add("storage_quota", user.getStorageQuota())
.add("storage_current", user.getStorageCurrent());
// Base functions
JsonArrayBuilder baseFunctions = Json.createArrayBuilder();
for (String baseFunction : ((UserPrincipal) principal).getBaseFunctionSet()) {
baseFunctions.add(baseFunction);
}
// Groups
JsonArrayBuilder groups = Json.createArrayBuilder();
for (GroupDto groupDto : groupDtoList) {
groups.add(groupDto.getName());
}
response.add("base_functions", baseFunctions)
.add("groups", groups)
.add("is_default_password", hasBaseFunction(BaseFunction.ADMIN) && Constants.DEFAULT_ADMIN_PASSWORD.equals(user.getPassword()));
}
@ -495,8 +512,19 @@ public class UserResource extends BaseResource {
throw new ClientException("UserNotFound", "The user doesn't exist");
}
// Groups
GroupDao groupDao = new GroupDao();
List<GroupDto> groupDtoList = groupDao.findByCriteria(
new GroupCriteria().setUserId(user.getId()),
new SortCriteria(1, true));
JsonArrayBuilder groups = Json.createArrayBuilder();
for (GroupDto groupDto : groupDtoList) {
groups.add(groupDto.getName());
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("username", user.getUsername())
.add("groups", groups)
.add("email", user.getEmail())
.add("storage_quota", user.getStorageQuota())
.add("storage_current", user.getStorageCurrent());
@ -506,30 +534,37 @@ public class UserResource extends BaseResource {
/**
* Returns all active users.
*
* @param limit Page limit
* @param offset Page offset
* @param sortColumn Sort index
* @param asc If true, ascending sorting, else descending
* @param groupName Only return users from this group
* @return Response
*/
@GET
@Path("list")
public Response list(
@QueryParam("limit") Integer limit,
@QueryParam("offset") Integer offset,
@QueryParam("sort_column") Integer sortColumn,
@QueryParam("asc") Boolean asc) {
@QueryParam("asc") Boolean asc,
@QueryParam("group") String groupName) {
if (!authenticate()) {
throw new ForbiddenClientException();
}
JsonArrayBuilder users = Json.createArrayBuilder();
PaginatedList<UserDto> paginatedList = PaginatedLists.create(limit, offset);
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
// Validate the group
String groupId = null;
if (!Strings.isNullOrEmpty(groupName)) {
GroupDao groupDao = new GroupDao();
Group group = groupDao.getActiveByName(groupName);
if (group != null) {
groupId = group.getId();
}
}
UserDao userDao = new UserDao();
userDao.findByCriteria(paginatedList, new UserCriteria(), sortCriteria);
for (UserDto userDto : paginatedList.getResultList()) {
List<UserDto> userDtoList = userDao.findByCriteria(new UserCriteria().setGroupId(groupId), sortCriteria);
for (UserDto userDto : userDtoList) {
users.add(Json.createObjectBuilder()
.add("id", userDto.getId())
.add("username", userDto.getUsername())
@ -540,7 +575,6 @@ public class UserResource extends BaseResource {
}
JsonObjectBuilder response = Json.createObjectBuilder()
.add("total", paginatedList.getResultCount())
.add("users", users);
return Response.ok().entity(response.build()).build();
}
@ -558,14 +592,7 @@ public class UserResource extends BaseResource {
}
// Get the value of the session token
String authToken = null;
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (TokenBasedSecurityFilter.COOKIE_NAME.equals(cookie.getName())) {
authToken = cookie.getValue();
}
}
}
String authToken = getAuthToken();
JsonArrayBuilder sessions = Json.createArrayBuilder();
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
@ -600,14 +627,7 @@ public class UserResource extends BaseResource {
}
// Get the value of the session token
String authToken = null;
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (TokenBasedSecurityFilter.COOKIE_NAME.equals(cookie.getName())) {
authToken = cookie.getValue();
}
}
}
String authToken = getAuthToken();
// Remove other tokens
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
@ -618,4 +638,21 @@ public class UserResource extends BaseResource {
.add("status", "ok");
return Response.ok().entity(response.build()).build();
}
/**
* Returns the authentication token value.
*
* @return Token value
*/
private String getAuthToken() {
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (TokenBasedSecurityFilter.COOKIE_NAME.equals(cookie.getName())
&& !Strings.isNullOrEmpty(cookie.getValue())) {
return cookie.getValue();
}
}
}
return null;
}
}

View File

@ -106,6 +106,33 @@ angular.module('docs',
}
}
})
.state('settings.group', {
url: '/group',
views: {
'settings': {
templateUrl: 'partial/docs/settings.group.html',
controller: 'SettingsGroup'
}
}
})
.state('settings.group.edit', {
url: '/edit/:name',
views: {
'group': {
templateUrl: 'partial/docs/settings.group.edit.html',
controller: 'SettingsGroupEdit'
}
}
})
.state('settings.group.add', {
url: '/add',
views: {
'group': {
templateUrl: 'partial/docs/settings.group.edit.html',
controller: 'SettingsGroupEdit'
}
}
})
.state('settings.vocabulary', {
url: '/vocabulary',
views: {
@ -221,19 +248,37 @@ angular.module('docs',
url: '/user',
views: {
'page': {
templateUrl: 'partial/docs/user.html',
controller: 'User'
templateUrl: 'partial/docs/usergroup.html',
controller: 'UserGroup'
}
}
})
.state('user.profile', {
url: '/:username',
views: {
'user': {
'sub': {
templateUrl: 'partial/docs/user.profile.html',
controller: 'UserProfile'
}
}
})
.state('group', {
url: '/group',
views: {
'page': {
templateUrl: 'partial/docs/usergroup.html',
controller: 'UserGroup'
}
}
})
.state('group.profile', {
url: '/:name',
views: {
'sub': {
templateUrl: 'partial/docs/group.profile.html',
controller: 'GroupProfile'
}
}
});
// Configuring Restangular

View File

@ -50,6 +50,7 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
$scope.resetForm = function() {
$scope.document = {
tags: [],
relations: [],
language: 'fra'
};
$scope.newFiles = [];
@ -72,6 +73,9 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
// Extract ids from tags
document.tags = _.pluck(document.tags, 'id');
// Extract ids from relations (only when our document is the source)
document.relations = _.pluck(_.where(document.relations, { source: true }), 'id');
if ($scope.isEdit()) {
promise = Restangular.one('document', $stateParams.id).post('', document);
} else {

View File

@ -35,18 +35,21 @@ angular.module('docs').controller('DocumentViewPermissions', function ($scope, $
if ($scope.acl.perm == 'READWRITE') {
acls = [{
source: $stateParams.id,
username: $scope.acl.username,
perm: 'READ'
target: $scope.acl.target.name,
perm: 'READ',
type: $scope.acl.target.type
}, {
source: $stateParams.id,
username: $scope.acl.username,
perm: 'WRITE'
target: $scope.acl.target.name,
perm: 'WRITE',
type: $scope.acl.target.type
}];
} else {
acls = [{
source: $stateParams.id,
username: $scope.acl.username,
perm: $scope.acl.perm
target: $scope.acl.target.name,
perm: $scope.acl.perm,
type: $scope.acl.target.type
}];
}
@ -74,7 +77,20 @@ angular.module('docs').controller('DocumentViewPermissions', function ($scope, $
.get({
search: $viewValue
}).then(function(data) {
deferred.resolve(_.pluck(data.users, 'username'), true);
var output = [];
// Add the type to use later
output.push.apply(output, _.map(data.users, function(user) {
user.type = 'USER';
return user;
}));
output.push.apply(output, _.map(data.groups, function(group) {
group.type = 'GROUP';
return group;
}));
// Send the data to the typeahead directive
deferred.resolve(output, true);
});
return deferred.promise;
};

View File

@ -0,0 +1,11 @@
'use strict';
/**
* Group profile controller.
*/
angular.module('docs').controller('GroupProfile', function($stateParams, Restangular, $scope) {
// Load user
Restangular.one('group', $stateParams.name).get().then(function(data) {
$scope.group = data;
});
});

View File

@ -0,0 +1,24 @@
'use strict';
/**
* Settings group page controller.
*/
angular.module('docs').controller('SettingsGroup', function($scope, $state, Restangular) {
/**
* Load groups from server.
*/
$scope.loadGroups = function() {
Restangular.one('group').get().then(function(data) {
$scope.groups = data.groups;
});
};
$scope.loadGroups();
/**
* Edit a group.
*/
$scope.editGroup = function(group) {
$state.go('settings.group.edit', { name: group.name });
};
});

View File

@ -0,0 +1,128 @@
'use strict';
/**
* Settings group edition page controller.
*/
angular.module('docs').controller('SettingsGroupEdit', function($scope, $dialog, $state, $stateParams, Restangular, $q) {
/**
* Returns true if in edit mode (false in add mode).
*/
$scope.isEdit = function() {
return $stateParams.name;
};
/**
* In edit mode, load the current group.
*/
if ($scope.isEdit()) {
Restangular.one('group', $stateParams.name).get().then(function(data) {
$scope.group = data;
});
}
/**
* Update the current group.
*/
$scope.edit = function() {
var promise = null;
var group = angular.copy($scope.group);
if ($scope.isEdit()) {
promise = Restangular
.one('group', $stateParams.name)
.post('', group);
} else {
promise = Restangular
.one('group')
.put(group);
}
promise.then(function() {
$scope.loadGroups();
if ($scope.isEdit()) {
$state.go('settings.group');
} else {
// Go to edit this group to add members
$state.go('settings.group.edit', { name: group.name });
}
});
};
/**
* Delete the current group.
*/
$scope.remove = function() {
var title = 'Delete group';
var msg = 'Do you really want to delete this group?';
var btns = [{result:'cancel', label: 'Cancel'}, {result:'ok', label: 'OK', cssClass: 'btn-primary'}];
$dialog.messageBox(title, msg, btns, function(result) {
if (result == 'ok') {
Restangular.one('group', $stateParams.name).remove().then(function() {
$scope.loadGroups();
$state.go('settings.group');
}, function() {
$state.go('settings.group');
});
}
});
};
/**
* Returns a promise for typeahead group.
*/
$scope.getGroupTypeahead = function($viewValue) {
var deferred = $q.defer();
Restangular.one('group')
.getList('', {
sort_column: 1,
asc: true
}).then(function(data) {
deferred.resolve(_.pluck(_.filter(data.groups, function(group) {
return group.name.indexOf($viewValue) !== -1;
}), 'name'));
});
return deferred.promise;
};
/**
* Returns a promise for typeahead user.
*/
$scope.getUserTypeahead = function($viewValue) {
var deferred = $q.defer();
Restangular.one('user')
.getList('list', {
search: $viewValue,
sort_column: 1,
asc: true
}).then(function(data) {
deferred.resolve(_.pluck(_.filter(data.users, function(user) {
return user.username.indexOf($viewValue) !== -1;
}), 'username'));
});
return deferred.promise;
};
/**
* Add a new member.
*/
$scope.addMember = function(member) {
$scope.member = '';
Restangular.one('group/' + $stateParams.name).put({
username: member
}).then(function() {
if ($scope.group.members.indexOf(member) === -1) {
$scope.group.members.push(member);
}
});
};
/**
* Remove a member.
*/
$scope.removeMember = function(member) {
Restangular.one('group/' + $stateParams.name, member).remove().then(function() {
$scope.group.members.splice($scope.group.members.indexOf(member), 1);
});
};
});

View File

@ -8,7 +8,10 @@ angular.module('docs').controller('SettingsUser', function($scope, $state, Resta
* Load users from server.
*/
$scope.loadUsers = function() {
Restangular.one('user/list').get({ limit: 100 }).then(function(data) {
Restangular.one('user/list').get({
sort_column: 1,
asc: true
}).then(function(data) {
$scope.users = data.users;
});
};

Some files were not shown because too many files have changed in this diff Show More