Merge remote-tracking branch 'origin/master'

This commit is contained in:
Jean-Marc Tremeaux 2018-10-23 18:16:35 +02:00
commit 94252de73f
451 changed files with 115713 additions and 22663 deletions

5
.gitignore vendored
View File

@ -9,3 +9,8 @@
/.idea
/.project
*.iml
node_modules
import_test
docs-importer-linux
docs-importer-macos
docs-importer-win.exe

View File

@ -2,10 +2,22 @@ sudo: required
dist: trusty
language: java
before_install:
- sudo add-apt-repository -y ppa:mc3man/trusty-media
- sudo apt-get -qq update
- sudo apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn
- sudo apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra
- sudo apt-get -y -q install haveged && sudo service haveged start
after_success:
- mvn -Pprod -DskipTests clean install
- docker login -u $DOCKER_USER -p $DOCKER_PASS
- export REPO=sismics/docs
- export TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi`
- docker build -f Dockerfile -t $REPO:$COMMIT .
- docker tag $REPO:$COMMIT $REPO:$TAG
- docker tag $REPO:$COMMIT $REPO:travis-$TRAVIS_BUILD_NUMBER
- docker push $REPO
env:
global:
- TESSDATA_PREFIX=/usr/share/tesseract-ocr
- LC_NUMERIC=C
- secure: LRGpjWORb0qy6VuypZjTAfA8uRHlFUMTwb77cenS9PPRBxuSnctC531asS9Xg3DqC5nsRxBBprgfCKotn5S8nBSD1ceHh84NASyzLSBft3xSMbg7f/2i7MQ+pGVwLncusBU6E/drnMFwZBleo+9M8Tf96axY5zuUp90MUTpSgt0=
- secure: bCDDR6+I7PmSkuTYZv1HF/z98ANX/SFEESUCqxVmV5Gs0zFC0vQXaPJQ2xaJNRop1HZBFMZLeMMPleb0iOs985smpvK2F6Rbop9Tu+Vyo0uKqv9tbZ7F8Nfgnv9suHKZlL84FNeUQZJX6vsFIYPEJ/r7K5P/M0PdUy++fEwxEhU=
- secure: ewXnzbkgCIHpDWtaWGMa1OYZJ/ki99zcIl4jcDPIC0eB3njX/WgfcC6i0Ke9mLqDqwXarWJ6helm22sNh+xtQiz6isfBtBX+novfRt9AANrBe3koCMUemMDy7oh5VflBaFNP0DVb8LSCnwf6dx6ZB5E9EB8knvk40quc/cXpGjY=
- COMMIT=${TRAVIS_COMMIT::8}

46
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,46 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@sismicsdocs.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@ -1,11 +1,11 @@
FROM sismics/jetty:9.2.20-jdk7
MAINTAINER benjamin.gam@gmail.com
FROM sismics/ubuntu-jetty:9.4.12
MAINTAINER b.gamard@sismics.com
RUN apt-get update && apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn && \
RUN apt-get update && apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra && \
apt-get clean && rm -rf /var/lib/apt/lists/*
ENV TESSDATA_PREFIX /usr/share/tesseract-ocr
ENV LC_NUMERIC C
# Remove the embedded javax.mail jar from Jetty
RUN rm -f /opt/jetty/lib/mail/javax.mail.glassfish-*.jar
ADD docs-web/target/docs-web-*.war /opt/jetty/webapps/docs.war
ADD docs.xml /opt/jetty/webapps/docs.xml
ADD docs-web/target/docs-web-*.war /opt/jetty/webapps/docs.war

View File

@ -1,52 +1,89 @@
Sismics Docs [![Build Status](https://secure.travis-ci.org/sismics/docs.png)](http://travis-ci.org/sismics/docs)
============
<h3 align="center">
<img src="https://www.sismicsdocs.com/img/github-title.png" alt="Sismics Docs" width=500 />
</h3>
_Web interface_
[![Twitter: @sismicsdocs](https://img.shields.io/badge/contact-@sismicsdocs-blue.svg?style=flat)](https://twitter.com/sismicsdocs)
[![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
[![Build Status](https://secure.travis-ci.org/sismics/docs.png)](http://travis-ci.org/sismics/docs)
![Web interface](http://sismics.com/docs/screenshot1.png)
Docs is an open source, lightweight document management system for individuals and businesses.
_Android application_
**Discuss it on [Product Hunt](https://www.producthunt.com/posts/sismics-docs) 🦄**
![Android documents list](http://sismics.com/docs/android1.png) ![Android navigation](http://sismics.com/docs/android2.png) ![Android document details](http://sismics.com/docs/android3.png) ![Android document actions](http://sismics.com/docs/android4.png)
<hr />
<h2 align="center">
✨ We just launched a Cloud version of Sismics Docs! Head to <a href="https://www.sismicsdocs.com/">sismicsdocs.com</a> for more informations ✨
</h2>
<hr />
What is Docs?
---------------
![New!](https://www.sismicsdocs.com/img/laptop-demo.png?20180301)
Docs is an open source, lightweight document management system.
Demo
----
Docs is written in Java, and may be run on any operating system with Java support.
A demo is available at [demo.sismicsdocs.com](https://demo.sismicsdocs.com)
- Guest login is enabled with read access on all documents
- "admin" login with "admin" password
- "demo" login with "password" password
Features
--------
- Responsive user interface
- Optical character recognition
- Support image, PDF, ODT and DOCX files
- Flexible search engine
- Support image, PDF, ODT, DOCX, PPTX files
- Video file support ![New!](https://www.sismics.com/public/img/new.png)
- Flexible search engine with suggestions and highlighting
- Full text search in all supported files
- All [Dublin Core](http://dublincore.org/) metadata
- Workflow system ![New!](https://www.sismics.com/public/img/new.png)
- 256-bit AES encryption of stored files
- Tag system with nesting
- Import document from email (EML format) ![New!](https://www.sismics.com/public/img/new.png)
- Automatic inbox scanning and importing ![New!](https://www.sismics.com/public/img/new.png)
- User/group permission system
- 2-factor authentication
- Hierarchical groups
- Audit log
- Comments
- Storage quota per user
- Document sharing by URL
- RESTful Web API
- Webhooks to trigger external service ![New!](https://www.sismics.com/public/img/new.png)
- Fully featured Android client
- Tested to 100k documents
- [Bulk files importer](https://github.com/sismics/docs/tree/master/docs-importer) (single or scan mode) ![New!](https://www.sismics.com/public/img/new.png)
- Tested to one million documents
Download
--------
Install with Docker
-------------------
The latest release is downloadable here: <https://github.com/sismics/docs/releases> in WAR format.
You will need a Java webapp server to run it, like [Jetty](http://eclipse.org/jetty/) or [Tomcat](http://tomcat.apache.org/)
From a Docker host, run this command to download and install Sismics Docs. The server will run on <http://[your-docker-host-ip]:8100>.
**The default admin password is "admin". Don't forget to change it before going to production.**
docker run --rm --name sismics_docs_latest -d -e DOCS_BASE_URL='http://[your-docker-host-ip]:8100' -p 8100:8080 -v sismics_docs_latest:/data sismics/docs:latest
<img src="http://www.newdesignfile.com/postpic/2011/01/green-info-icon_206509.png" width="16px" height="16px"> **Note:** You will need to change [your-docker-host-ip] with the IP address or FQDN of your docker host e.g.
FQDN: http://docs.sismics.com
IP: http://192.168.100.10
Manual installation
-------------------
#### Requirements
- Java 8 with the [Java Cryptography Extension](http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html)
- Tesseract 3 or 4 for OCR
- ffmpeg for video thumbnails
- mediainfo for video metadata extraction
- A webapp server like [Jetty](http://eclipse.org/jetty/) or [Tomcat](http://tomcat.apache.org/)
#### Download
The latest release is downloadable here: <https://github.com/sismics/docs/releases> in WAR format.
**The default admin password is "admin". Don't forget to change it before going to production.**
How to build Docs from the sources
----------------------------------
Prerequisites: JDK 7 with JCE, Maven 3, Tesseract 3.02
Prerequisites: JDK 8 with JCE, Maven 3, Tesseract 3 or 4
Docs is organized in several Maven modules:
@ -77,6 +114,24 @@ From the `docs-web` directory:
You will get your deployable WAR in the `docs-web/target` directory.
Contributing
------------
All contributions are more than welcomed. Contributions may close an issue, fix a bug (reported or not reported), improve the existing code, add new feature, and so on.
The `master` branch is the default and base branch for the project. It is used for development and all Pull Requests should go there.
Community
---------
Get updates on Sismics Docs' development and chat with the project maintainers:
- Follow [@sismicsdocs on Twitter](https://twitter.com/sismicsdocs)
- Read and subscribe to [The Official Sismics Docs Blog](https://blog.sismicsdocs.com/)
- Check the [Official Website](https://www.sismicsdocs.com)
- Join us [on Facebook](https://www.facebook.com/sismicsdocs)
License
-------

View File

@ -1,57 +1,43 @@
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
apply plugin: 'com.android.application'
repositories {
jcenter()
google()
}
android {
compileSdkVersion 24
buildToolsVersion '24'
compileSdkVersion 28
defaultConfig {
minSdkVersion 14
targetSdkVersion 24
targetSdkVersion 28
versionCode 1
versionName '1.0'
}
signingConfigs {
release {
storeFile file(System.getenv('TRACKINO_STORE_PATH'))
storePassword System.getenv('TRACKINO_STORE_PASS')
keyAlias System.getenv('TRACKINO_STORE_ALIAS')
keyPassword System.getenv('TRACKINO_STORE_KEYPASS')
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
lintOptions {
abortOnError false
}
}
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.android.support:appcompat-v7:24.0.0'
compile 'com.android.support:recyclerview-v7:24.0.0'
compile 'com.android.support:design:24.0.0'
compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
compile 'org.greenrobot:eventbus:3.0.0'
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.squareup.okhttp3:okhttp:3.4.0'
compile 'com.squareup.okhttp3:okhttp-urlconnection:3.4.0'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.0.2'
implementation fileTree(dir: 'libs', include: '*.jar')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5'
implementation 'org.greenrobot:eventbus:3.1.1'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.10.0'
implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
}

View File

@ -28,6 +28,7 @@
android:name=".activity.MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustNothing">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
@ -43,6 +44,9 @@
<activity
android:name=".activity.DocumentViewActivity"
android:label="">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
<activity
android:name=".activity.DocumentEditActivity"

View File

@ -9,11 +9,13 @@ import android.provider.SearchRecentSuggestions;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView;
@ -61,7 +63,10 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.main_activity);
// Enable ActionBar app icon to behave as action to toggle nav drawer
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
drawerLayout = findViewById(R.id.drawer_layout);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitleTextColor(getResources().getColor(android.R.color.white));
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
@ -75,15 +80,15 @@ public class MainActivity extends AppCompatActivity {
// Fill the drawer user info
JSONObject userInfo = ApplicationContext.getInstance().getUserInfo();
TextView usernameTextView = (TextView) findViewById(R.id.usernameTextView);
TextView usernameTextView = findViewById(R.id.usernameTextView);
usernameTextView.setText(userInfo.optString("username"));
TextView emailTextView = (TextView) findViewById(R.id.emailTextView);
TextView emailTextView = findViewById(R.id.emailTextView);
emailTextView.setText(userInfo.optString("email"));
// Get tag list to fill the drawer
final ListView tagListView = (ListView) findViewById(R.id.tagListView);
final ListView tagListView = findViewById(R.id.tagListView);
final View tagProgressView = findViewById(R.id.tagProgressView);
final TextView tagEmptyView = (TextView) findViewById(R.id.tagEmptyView);
final TextView tagEmptyView = findViewById(R.id.tagEmptyView);
tagListView.setEmptyView(tagProgressView);
JSONObject cacheTags = PreferenceUtil.getCachedJson(this, PreferenceUtil.PREF_CACHED_TAGS_JSON);
if (cacheTags != null) {
@ -145,6 +150,15 @@ public class MainActivity extends AppCompatActivity {
}
});
// Add document button
ImageButton addDocumentButton = findViewById(R.id.addDocumentButton);
addDocumentButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, DocumentEditActivity.class));
}
});
handleIntent(getIntent());
EventBus.getDefault().register(this);

View File

@ -33,7 +33,6 @@ public class LanguageAdapter extends BaseAdapter {
}
languageList.add(new Language("fra", R.string.language_french, R.drawable.fra));
languageList.add(new Language("eng", R.string.language_english, R.drawable.eng));
languageList.add(new Language("jpn", R.string.language_japanese, R.drawable.jpn));
}
@Override

View File

@ -2,6 +2,7 @@ package com.sismics.docs.fragment;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
@ -9,12 +10,10 @@ import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import com.sismics.docs.R;
import com.sismics.docs.activity.DocumentEditActivity;
import com.sismics.docs.activity.DocumentViewActivity;
import com.sismics.docs.adapter.DocListAdapter;
import com.sismics.docs.event.DocumentAddEvent;
@ -46,11 +45,6 @@ public class DocListFragment extends Fragment {
*/
private String query;
/**
* Request code of adding document.
*/
private static final int REQUEST_CODE_ADD_DOCUMENT = 1;
// View cache
private EmptyRecyclerView recyclerView;
private SwipeRefreshLayout swipeRefreshLayout;
@ -60,11 +54,11 @@ public class DocListFragment extends Fragment {
private int previousTotal = 0;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.doc_list_fragment, container, false);
// Configure the RecyclerView
recyclerView = (EmptyRecyclerView) view.findViewById(R.id.docList);
recyclerView = view.findViewById(R.id.docList);
adapter = new DocListAdapter();
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true);
@ -122,16 +116,6 @@ public class DocListFragment extends Fragment {
}
});
// Add document button
ImageButton addDocumentButton = (ImageButton) view.findViewById(R.id.addDocumentButton);
addDocumentButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), DocumentEditActivity.class);
startActivityForResult(intent, REQUEST_CODE_ADD_DOCUMENT);
}
});
// Grab the documents
loadDocuments(view, true);

View File

@ -0,0 +1,47 @@
package com.sismics.docs.ui;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.annotation.NonNull;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.util.AttributeSet;
import android.view.View;
import com.sismics.docs.R;
public class ScrollingFABBehavior extends CoordinatorLayout.Behavior<FloatingActionButton> {
private int toolbarHeight;
public ScrollingFABBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
this.toolbarHeight = getToolbarHeight(context);
}
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull FloatingActionButton fab, @NonNull View dependency) {
return dependency instanceof AppBarLayout;
}
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull FloatingActionButton fab, @NonNull View dependency) {
if (dependency instanceof AppBarLayout) {
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
int fabBottomMargin = lp.bottomMargin;
int distanceToScroll = fab.getHeight() + fabBottomMargin;
float ratio = dependency.getY() /(float) toolbarHeight;
fab.setTranslationY(- distanceToScroll * ratio);
}
return true;
}
private int getToolbarHeight(Context context) {
final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
new int[] { R.attr.actionBarSize });
int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
styledAttributes.recycle();
return toolbarHeight;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 B

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -37,17 +36,4 @@
android:textSize="16sp"
android:layout_centerInParent="true"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/addDocumentButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="20dp"
android:src="@drawable/ic_add_white_24dp"
app:fabSize="normal"/>
</RelativeLayout>

View File

@ -6,11 +6,47 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/main_fragment"
<android.support.design.widget.CoordinatorLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/overview_coordinator_layout"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.sismics.docs.fragment.DocListFragment"/>
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:layout_scrollFlags="enterAlways|scroll|snap" />
</android.support.design.widget.AppBarLayout>
<fragment
android:id="@+id/main_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
class="com.sismics.docs.fragment.DocListFragment"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/addDocumentButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/ic_add_white_24dp"
app:layout_anchor="@id/main_fragment"
app:layout_behavior="com.sismics.docs.ui.ScrollingFABBehavior"
app:layout_anchorGravity="bottom|right|end"
app:fabSize="normal"/>
</android.support.design.widget.CoordinatorLayout>
<LinearLayout
android:id="@+id/left_drawer"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Validation -->
<string name="validate_error_email">Email invalide</string>
<string name="validate_error_length_min">Trop court (min. %d)</string>
<string name="validate_error_length_max">Trop long (max. %d)</string>
<string name="validate_error_required">Requis</string>
<string name="validate_error_alphanumeric">Seuls les lettres et les nombres sont autorisés</string>
<!-- App -->
<string name="drawer_open">Ouvrir le menu de navigation</string>
<string name="drawer_close">Fermer le menu de navigation</string>
<string name="login_explain"><![CDATA[Pour commencer, vous devez télécharger et installer le serveur Sismics Docs sur <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> et entrer son URL ci-dessous]]></string>
<string name="server">Serveur</string>
<string name="username">Nom d\'utilisateur</string>
<string name="password">Mot de passe</string>
<string name="login">Connexion</string>
<string name="ok">OK</string>
<string name="cancel">Annuler</string>
<string name="login_fail_title">Echec de la connexion</string>
<string name="login_fail">Mauvais nom d\'utilisateur ou de mot de passe</string>
<string name="network_error_title">Erreur réseau</string>
<string name="network_error">Erreur réseau, veuillez vérifier votre connexion internet et l\'URL du serveur</string>
<string name="invalid_url_title">URL invalide</string>
<string name="invalid_url">Veuillez vérifier l\URL du serveur et réessayer</string>
<string name="crash_toast_text">Une erreur s\'est produite, un rapport a été envoyé afin de corriger ce problème</string>
<string name="created_date">Date création</string>
<string name="download_file">Télécharger ce fichier</string>
<string name="download_document">Télécharger</string>
<string name="action_search">Rechercher dans les documents</string>
<string name="all_documents">Tous les documents</string>
<string name="shared_documents">Documents partagés</string>
<string name="all_tags">Tous les tags</string>
<string name="no_tags">Aucun tag</string>
<string name="error_loading_tags">Erreur de chargement des tags</string>
<string name="no_documents">Aucun document</string>
<string name="error_loading_documents">Erreur de chargement des documents</string>
<string name="no_files">Aucun fichier</string>
<string name="error_loading_files">Erreur de chargement des fichiers</string>
<string name="new_document">Nouveau document</string>
<string name="share">Partage</string>
<string name="close">Fermer</string>
<string name="add">Ajouter</string>
<string name="add_share_hint">Nom du partage (facultatif)</string>
<string name="document_not_shared">Ce document n\'est pas partagé</string>
<string name="delete_share">Supprimer ce partage</string>
<string name="send_share">Envoi ce lien de partage</string>
<string name="error_loading_shares">Erreur de chargement des partages</string>
<string name="error_adding_share">Erreur lors de l\'ajout du partage</string>
<string name="share_default_name">Lien de partage</string>
<string name="error_deleting_share">Erreur lors de la suppression de ce partage</string>
<string name="send_share_to">Envoi ce lien de partage à</string>
<string name="upload_file">Aj. fichier</string>
<string name="upload_from">Envoyer un fichier depuis</string>
<string name="settings">Paramètres</string>
<string name="logout">Déconnexion</string>
<string name="version">Version</string>
<string name="build">Build</string>
<string name="pref_advanced_category">Paramètres avancés</string>
<string name="pref_about_category">A propors</string>
<string name="pref_github">GitHub</string>
<string name="pref_issue">Rapporter un bug</string>
<string name="pref_clear_cache_title">Vider le cache</string>
<string name="pref_clear_cache_summary">Nettoyer les fichiers en cache</string>
<string name="pref_clear_cache_success">Cache vidé</string>
<string name="pref_clear_history_title">Vider l\'historique de recherche</string>
<string name="pref_clear_history_summary">Supprimer les recherches récentes</string>
<string name="pref_clear_history_success">Historique de recherche vidé</string>
<string name="pref_cache_size">Taille du cache</string>
<string name="save">Enregistrer</string>
<string name="edit_document">Modifier</string>
<string name="error_editing_document">Erreur réseau, veuillez réessayer</string>
<string name="please_wait">Veuillez patienter</string>
<string name="document_editing_message">Envoi des données</string>
<string name="delete_document">Suppr.</string>
<string name="delete_document_title">Supprimer le document</string>
<string name="delete_document_message">Etes-vous sûr de vouloir supprimer ce document et tous les fichiers associés ?</string>
<string name="document_delete_failure">Erreur réseau lors de la suppression de ce document</string>
<string name="document_deleting_message">Suppression du document</string>
<string name="delete_file_title">Supprimer le fichier</string>
<string name="delete_file_message">Etes-vous sûr de vouloir supprimer ce fichier ?</string>
<string name="file_delete_failure">Erreur réseau lors de la suppression du fichier</string>
<string name="file_deleting_message">Suppression du fichier</string>
<string name="error_reading_file">Erreur lors de la lecture du fichier</string>
<string name="upload_notification_title">Sismics Docs</string>
<string name="upload_notification_message">Envoi du nouveau fichier</string>
<string name="upload_notification_error">Erreur lors de l\'envoi du nouveau fichier</string>
<string name="delete_file">Supprimer ce fichier</string>
<string name="advanced_search">Recherche avancée</string>
<string name="search">Rechercher</string>
<string name="add_tags">Ajouter des tags</string>
<string name="creation_date">Date de création</string>
<string name="description">Description</string>
<string name="title">Titre</string>
<string name="simple_search">Recherche simple</string>
<string name="fulltext_search">Recherche texte intégral</string>
<string name="creator">Créateur</string>
<string name="after_date">Après cette date</string>
<string name="before_date">Avant cette date</string>
<string name="search_tags">Rechercher dans les tags</string>
<string name="all_languages">Toutes les langues</string>
<string name="toggle_informations">Afficher/masquer les informations</string>
<string name="who_can_access">Qui a accès</string>
<string name="comments">Commentaires</string>
<string name="no_comments">Aucun commentaire</string>
<string name="error_loading_comments">Erreur de chargement des commentaires</string>
<string name="send">Envoyer</string>
<string name="add_comment">Ajouter un commentaire</string>
<string name="comment_add_failure">Erreur lors de l\'ajout du commentaire</string>
<string name="adding_comment">Ajout du commentaire</string>
<string name="comment_delete">Supprimer le commentaire</string>
<string name="deleting_comment">Suppression du commentaire</string>
<string name="error_deleting_comment">Erreur lors de la suppression du commentaire</string>
<string name="export_pdf">PDF</string>
<string name="download">Télécharger</string>
<string name="margin">Marge</string>
<string name="fit_image_to_page">Ajuster les images à la page</string>
<string name="export_comments">Exporter les commentaires</string>
<string name="export_metadata">Exporter les métadonnées</string>
<string name="mm">mm</string>
<string name="download_file_title">Export de fichier Sismics Docs</string>
<string name="download_document_title">Export de document Sismics Docs</string>
<string name="download_pdf_title">Export PDF Sismics Docs</string>
<string name="latest_activity">Activité récente</string>
<string name="activity">Activité</string>
<string name="email">E-mail</string>
<string name="storage_quota">Quota de stockage</string>
<string name="storage_display">%1$d/%2$d Mo</string>
<string name="validation_code">Code de validation</string>
<string name="shared">Partagé</string>
<string name="language">Langue</string>
<string name="coverage">Couverture</string>
<string name="type">Type</string>
<string name="source">Source</string>
<string name="format">Format</string>
<string name="publisher">Editeur</string>
<string name="identifier">Identifiant</string>
<string name="subject">Sujet</string>
<string name="rights">Droits</string>
<string name="contributors">Contributeurs</string>
<string name="relations">Relations</string>
</resources>

View File

@ -9,10 +9,10 @@
<string name="validate_error_alphanumeric">Only letters and numbers</string>
<!-- App -->
<string name="app_name">Sismics Docs</string>
<string name="app_name" translatable="false">Sismics Docs</string>
<string name="drawer_open">Open navigation drawer</string>
<string name="drawer_close">Close navigation drawer</string>
<string name="login_explain"><![CDATA[To start, you must download and install Sismics Docs Server on <a href="http://www.sismics.com/docs">www.sismics.com/docs</a> and enter your server URL below]]></string>
<string name="login_explain"><![CDATA[To start, you must download and install Sismics Docs Server on <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> and enter its below]]></string>
<string name="server">Server</string>
<string name="username">Username</string>
<string name="password">Password</string>
@ -46,7 +46,7 @@
<string name="add_share_hint">Name the share (optional)</string>
<string name="document_not_shared">This document is not currently shared</string>
<string name="delete_share">Delete this share</string>
<string name="send_share">Send this share</string>
<string name="send_share">Send this share link</string>
<string name="error_loading_shares">Error loading shares</string>
<string name="error_adding_share">Error adding share</string>
<string name="share_default_name">Share link</string>
@ -69,9 +69,8 @@
<string name="pref_clear_history_summary">Wipe the recent search suggestions</string>
<string name="pref_clear_history_success">Search history cleared</string>
<string name="pref_cache_size">Cache size</string>
<string name="language_french">French</string>
<string name="language_english">English</string>
<string name="language_japanese">Japanese</string>
<string name="language_french" translatable="false">Français</string>
<string name="language_english" translatable="false">English</string>
<string name="save">Save</string>
<string name="edit_document">Edit</string>
<string name="error_editing_document">Network error, please try again</string>

View File

@ -6,6 +6,14 @@
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppThemeDark" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>

View File

@ -1,6 +1,6 @@
#Fri Dec 23 21:14:18 CET 2016
#Thu Oct 18 22:37:49 CEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip

View File

@ -5,7 +5,7 @@
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.5-SNAPSHOT</version>
<version>1.6-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
@ -51,7 +51,27 @@
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
@ -91,6 +111,26 @@
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-suggest</artifactId>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<!-- Only there to read old index and rebuild them -->
<dependency>
@ -113,35 +153,41 @@
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.odftoolkit.odfdom.converter.pdf</artifactId>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.apache.poi.xwpf.converter.pdf</artifactId>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.odfdom.converter.pdf</artifactId>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.poi.xwpf.converter.pdf</artifactId>
</dependency>
<!-- ImageIO plugins -->
<dependency>
<groupId>com.levigo.jbig2</groupId>
<artifactId>levigo-jbig2-imageio</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
</dependency>
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-core</artifactId>
<artifactId>jai-imageio-jpeg2000</artifactId>
</dependency>
<dependency>
<groupId>com.levigo.jbig2</groupId>
<artifactId>levigo-jbig2-imageio</artifactId>
</dependency>
<!-- Only for connecting to PostgreSQL database -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- Test dependencies -->
@ -184,20 +230,6 @@
<profile>
<id>prod</id>
</profile>
<!-- Demo profile -->
<profile>
<id>demo</id>
<build>
<resources>
<resource>
<directory>src/demo/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</profile>
</profiles>
<build>

View File

@ -0,0 +1,18 @@
package com.sismics.docs.core.constant;
/**
* ACL type.
*
* @author bgamard
*/
public enum AclType {
/**
* User created ACL.
*/
USER,
/**
* ACL created by the routing module.
*/
ROUTING
}

View File

@ -0,0 +1,23 @@
package com.sismics.docs.core.constant;
/**
* Action types.
*
* @author bgamard
*/
public enum ActionType {
/**
* Add a tag.
*/
ADD_TAG,
/**
* Remove a tag.
*/
REMOVE_TAG,
/**
* Process files.
*/
PROCESS_FILES
}

View File

@ -18,5 +18,29 @@ public enum ConfigType {
/**
* Guest login.
*/
GUEST_LOGIN
GUEST_LOGIN,
/**
* Default language.
*/
DEFAULT_LANGUAGE,
/**
* SMTP server configuration.
*/
SMTP_HOSTNAME,
SMTP_PORT,
SMTP_FROM,
SMTP_USERNAME,
SMTP_PASSWORD,
/**
* Inbox scanning configuration.
*/
INBOX_ENABLED,
INBOX_HOSTNAME,
INBOX_PORT,
INBOX_USERNAME,
INBOX_PASSWORD,
INBOX_TAG
}

View File

@ -1,9 +1,9 @@
package com.sismics.docs.core.constant;
import java.util.List;
import com.google.common.collect.Lists;
import java.util.List;
/**
* Application constants.
*
@ -21,14 +21,9 @@ public class Constants {
public static final String DEFAULT_ADMIN_PASSWORD = "$2a$05$6Ny3TjrW3aVAL1or2SlcR.fhuDgPKp5jp.P9fBXwVNePgeLqb4i3C";
/**
* RAM Lucene directory storage.
* Administrator's default email.
*/
public static final String LUCENE_DIRECTORY_STORAGE_RAM = "RAM";
/**
* File Lucene directory storage.
*/
public static final String LUCENE_DIRECTORY_STORAGE_FILE = "FILE";
public static final String DEFAULT_ADMIN_EMAIL = "admin@localhost";
/**
* Guest user ID.
@ -43,5 +38,58 @@ public class Constants {
/**
* Supported document languages.
*/
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "jpn");
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor");
/**
* Base URL environment variable.
*/
public static final String BASE_URL_ENV = "DOCS_BASE_URL";
/**
* Default language environment variable.
*/
public static final String DEFAULT_LANGUAGE_ENV = "DOCS_DEFAULT_LANGUAGE";
/**
* SMTP configuration environment variables.
*/
public static final String SMTP_HOSTNAME_ENV = "DOCS_SMTP_HOSTNAME";
public static final String SMTP_PORT_ENV = "DOCS_SMTP_PORT";
public static final String SMTP_USERNAME_ENV = "DOCS_SMTP_USERNAME";
public static final String SMTP_PASSWORD_ENV = "DOCS_SMTP_PASSWORD";
/**
* Global quota environment variable.
*/
public static final String GLOBAL_QUOTA_ENV = "DOCS_GLOBAL_QUOTA";
/**
* Initial admin password environment variable.
*/
public static final String ADMIN_PASSWORD_INIT_ENV = "DOCS_ADMIN_PASSWORD_INIT";
/**
* Initial admin password environment variable.
*/
public static final String ADMIN_EMAIL_INIT_ENV = "DOCS_ADMIN_EMAIL_INIT";
/**
* Expiration time of the password recovery in hours.
*/
public static final int PASSWORD_RECOVERY_EXPIRATION_HOUR = 2;
/**
* Email template for password recovery.
*/
public static final String EMAIL_TEMPLATE_PASSWORD_RECOVERY = "password_recovery";
/**
* Email template for route step validate.
*/
public static final String EMAIL_TEMPLATE_ROUTE_STEP_VALIDATE = "route_step_validate";
/**
* mm per inch.
*/
public static float MM_PER_INCH = 1 / (10 * 2.54f) * 72f;
}

View File

@ -0,0 +1,23 @@
package com.sismics.docs.core.constant;
/**
* Route step transitions.
*
* @author bgamard
*/
public enum RouteStepTransition {
/**
* Route step approved.
*/
APPROVED,
/**
* Route step rejected.
*/
REJECTED,
/**
* Route step validated.
*/
VALIDATED
}

View File

@ -0,0 +1,18 @@
package com.sismics.docs.core.constant;
/**
* Route step types.
*
* @author bgamard
*/
public enum RouteStepType {
/**
* Approval step with 2 choices.
*/
APPROVE,
/**
* Simple validation step, no possible choice.
*/
VALIDATE
}

View File

@ -0,0 +1,15 @@
package com.sismics.docs.core.constant;
/**
* Webhook events.
*
* @author bgamard
*/
public enum WebhookEvent {
DOCUMENT_CREATED,
DOCUMENT_UPDATED,
DOCUMENT_DELETED,
FILE_CREATED,
FILE_UPDATED,
FILE_DELETED
}

View File

@ -1,30 +1,31 @@
package com.sismics.docs.core.dao.jpa;
package com.sismics.docs.core.dao;
import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.constant.AclType;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.dto.AclDto;
import com.sismics.docs.core.model.jpa.Acl;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.SecurityUtil;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.dto.AclDto;
import com.sismics.docs.core.model.jpa.Acl;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.util.context.ThreadLocalContext;
/**
* ACL DAO.
*
*
* @author bgamard
*/
public class AclDao {
/**
* Creates a new ACL.
*
*
* @param acl ACL
* @param userId User ID
* @return New ID
@ -32,20 +33,20 @@ public class AclDao {
public String create(Acl acl, String userId) {
// Create the UUID
acl.setId(UUID.randomUUID().toString());
// Create the ACL
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(acl);
// Create audit log
AuditLogUtil.create(acl, AuditLogType.CREATE, userId);
return acl.getId();
}
/**
* Search ACLs by target ID.
*
*
* @param targetId Target ID
* @return ACL list
*/
@ -54,32 +55,38 @@ public class AclDao {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select a from Acl a where a.targetId = :targetId and a.deleteDate is null");
q.setParameter("targetId", targetId);
return q.getResultList();
}
/**
* Search ACLs by source ID.
*
*
* @param sourceId Source ID
* @return ACL DTO list
*/
*/
@SuppressWarnings("unchecked")
public List<AclDto> getBySourceId(String sourceId) {
public List<AclDto> getBySourceId(String sourceId, AclType type) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
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 ");
StringBuilder sb = new StringBuilder("select a.ACL_ID_C, a.ACL_PERM_C, a.ACL_TARGETID_C, ")
.append(" u.USE_USERNAME_C, s.SHA_ID_C, s.SHA_NAME_C, g.GRP_NAME_C ")
.append(" from T_ACL a ")
.append(" left join T_USER u on u.USE_ID_C = a.ACL_TARGETID_C ")
.append(" left join T_SHARE s on s.SHA_ID_C = a.ACL_TARGETID_C ")
.append(" left join T_GROUP g on g.GRP_ID_C = a.ACL_TARGETID_C ")
.append(" where a.ACL_DELETEDATE_D is null and a.ACL_SOURCEID_C = :sourceId ");
if (type != null) {
sb.append(" and a.ACL_TYPE_C = :type");
}
// Perform the query
Query q = em.createNativeQuery(sb.toString());
q.setParameter("sourceId", sourceId);
if (type != null) {
q.setParameter("type", type.name());
}
List<Object[]> l = q.getResultList();
// Assemble results
List<AclDto> aclDtoList = new ArrayList<>();
for (Object[] o : l) {
@ -108,22 +115,27 @@ public class AclDao {
}
return aclDtoList;
}
/**
* Check if a source is accessible to a target.
*
*
* @param sourceId ACL source entity ID
* @param perm Necessary permission
* @param targetIdList List of targets
* @return True if the document is accessible
*/
public boolean checkPermission(String sourceId, PermType perm, List<String> targetIdList) {
if (SecurityUtil.skipAclCheck(targetIdList)) {
return true;
}
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select a.ACL_ID_C from T_ACL a ");
sb.append(" where a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = :sourceId and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null ");
sb.append(" union all ");
sb.append(" select a.ACL_ID_C from T_ACL a, T_DOCUMENT_TAG dt ");
sb.append(" where a.ACL_SOURCEID_C = dt.DOT_IDTAG_C and dt.DOT_IDDOCUMENT_C = :sourceId and dt.DOT_DELETEDATE_D is null ");
sb.append(" select a.ACL_ID_C from T_ACL a, T_DOCUMENT_TAG dt, T_DOCUMENT d ");
sb.append(" where a.ACL_SOURCEID_C = dt.DOT_IDTAG_C and dt.DOT_IDDOCUMENT_C = d.DOC_ID_C and dt.DOT_DELETEDATE_D is null ");
sb.append(" and d.DOC_ID_C = :sourceId and d.DOC_DELETEDATE_D is null ");
sb.append(" and a.ACL_TARGETID_C in (:targetIdList) and a.ACL_PERM_C = :perm and a.ACL_DELETEDATE_D is null ");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("sourceId", sourceId);
@ -136,32 +148,35 @@ public class AclDao {
/**
* Delete an ACL.
*
*
* @param sourceId Source ID
* @param perm Permission
* @param targetId Target ID
* @param userId User ID
* @param type Type
*/
@SuppressWarnings("unchecked")
public void delete(String sourceId, PermType perm, String targetId, String userId) {
public void delete(String sourceId, PermType perm, String targetId, String userId, AclType type) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Create audit log
Query q = em.createQuery("from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId");
Query q = em.createQuery("from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId and a.type = :type and a.deleteDate is null");
q.setParameter("sourceId", sourceId);
q.setParameter("perm", perm);
q.setParameter("targetId", targetId);
q.setParameter("type", type);
List<Acl> aclList = q.getResultList();
for (Acl acl : aclList) {
AuditLogUtil.create(acl, AuditLogType.DELETE, userId);
}
// Soft delete the ACLs
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId");
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId and a.type = :type and a.deleteDate is null");
q.setParameter("sourceId", sourceId);
q.setParameter("perm", perm);
q.setParameter("targetId", targetId);
q.setParameter("type", type);
q.setParameter("dateNow", new Date());
q.executeUpdate();
}
}
}

View File

@ -1,20 +1,10 @@
package com.sismics.docs.core.dao.jpa;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.persistence.EntityManager;
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.criteria.AuditLogCriteria;
import com.sismics.docs.core.dao.jpa.dto.AuditLogDto;
import com.sismics.docs.core.dao.criteria.AuditLogCriteria;
import com.sismics.docs.core.dao.dto.AuditLogDto;
import com.sismics.docs.core.model.jpa.AuditLog;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
@ -22,6 +12,10 @@ import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import java.sql.Timestamp;
import java.util.*;
/**
* Audit log DAO.
*

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa;
package com.sismics.docs.core.dao;
import com.sismics.docs.core.model.jpa.AuthenticationToken;
import com.sismics.util.context.ThreadLocalContext;
@ -86,7 +86,7 @@ public class AuthenticationTokenDao {
*/
public void updateLastConnectionDate(String id) {
StringBuilder sb = new StringBuilder("update T_AUTHENTICATION_TOKEN ato ");
sb.append(" set ato.AUT_LASTCONNECTIONDATE_D = :currentDate ");
sb.append(" set AUT_LASTCONNECTIONDATE_D = :currentDate ");
sb.append(" where ato.AUT_ID_C = :id");
EntityManager em = ThreadLocalContext.get().getEntityManager();

View File

@ -1,21 +1,20 @@
package com.sismics.docs.core.dao.jpa;
package com.sismics.docs.core.dao;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.dto.CommentDto;
import com.sismics.docs.core.model.jpa.Comment;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.dto.CommentDto;
import com.sismics.docs.core.model.jpa.Comment;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.util.context.ThreadLocalContext;
/**
* Comment DAO.
*

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa;
package com.sismics.docs.core.dao;
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.model.jpa.Config;

View File

@ -1,15 +1,14 @@
package com.sismics.docs.core.dao.jpa;
package com.sismics.docs.core.dao;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.sismics.docs.core.dao.dto.ContributorDto;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.sismics.docs.core.dao.jpa.dto.ContributorDto;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.util.context.ThreadLocalContext;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Contributor DAO.

View File

@ -1,32 +1,19 @@
package com.sismics.docs.core.dao.jpa;
package com.sismics.docs.core.dao;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.dto.DocumentDto;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.constant.PermType;
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.dao.lucene.LuceneDao;
import com.sismics.docs.core.model.jpa.Document;
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.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.UUID;
/**
* Document DAO.
@ -44,6 +31,7 @@ public class DocumentDao {
public String create(Document document, String userId) {
// Create the UUID
document.setId(UUID.randomUUID().toString());
document.setUpdateDate(new Date());
// Create the document
EntityManager em = ThreadLocalContext.get().getEntityManager();
@ -96,14 +84,14 @@ public class DocumentDao {
}
EntityManager em = ThreadLocalContext.get().getEntityManager();
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), ");
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_UPDATEDATE_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) shareCount, ");
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) fileCount, ");
sb.append(" u.USE_USERNAME_C ");
sb.append(" from T_DOCUMENT d ");
sb.append(" join T_USER u on d.DOC_IDUSER_C = u.USE_ID_C ");
sb.append(" where d.DOC_ID_C = :id and d.DOC_DELETEDATE_D is null ");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("id", id);
@ -128,6 +116,7 @@ public class DocumentDao {
documentDto.setCoverage((String) o[i++]);
documentDto.setRights((String) o[i++]);
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
documentDto.setUpdateTimestamp(((Timestamp) o[i++]).getTime());
documentDto.setLanguage((String) o[i++]);
documentDto.setShared(((Number) o[i++]).intValue() > 0);
documentDto.setFileCount(((Number) o[i++]).intValue());
@ -195,99 +184,6 @@ public class DocumentDao {
}
}
/**
* Searches documents by criteria.
*
* @param paginatedList List of documents (updated by side effects)
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @throws Exception
*/
public void findByCriteria(PaginatedList<DocumentDto> paginatedList, DocumentCriteria criteria, SortCriteria sortCriteria) throws Exception {
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
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 ");
// Add search criterias
if (criteria.getTargetIdList() != null) {
// Read permission is enough for searching
sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = d.DOC_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
sb.append(" left join T_DOCUMENT_TAG dta on dta.DOT_IDDOCUMENT_C = d.DOC_ID_C and dta.DOT_DELETEDATE_D is null ");
sb.append(" left join T_ACL a2 on a2.ACL_TARGETID_C in (:targetIdList) and a2.ACL_SOURCEID_C = dta.DOT_IDTAG_C and a2.ACL_PERM_C = 'READ' and a2.ACL_DELETEDATE_D is null ");
criteriaList.add("(a.ACL_ID_C is not null or a2.ACL_ID_C is not null)");
parameterMap.put("targetIdList", criteria.getTargetIdList());
}
if (!Strings.isNullOrEmpty(criteria.getSearch()) || !Strings.isNullOrEmpty(criteria.getFullSearch())) {
LuceneDao luceneDao = new LuceneDao();
Set<String> documentIdList = luceneDao.search(criteria.getSearch(), criteria.getFullSearch());
if (documentIdList.size() == 0) {
// If the search doesn't find any document, the request should return nothing
documentIdList.add(UUID.randomUUID().toString());
}
criteriaList.add("d.DOC_ID_C in :documentIdList");
parameterMap.put("documentIdList", documentIdList);
}
if (criteria.getCreateDateMin() != null) {
criteriaList.add("d.DOC_CREATEDATE_D >= :createDateMin");
parameterMap.put("createDateMin", criteria.getCreateDateMin());
}
if (criteria.getCreateDateMax() != null) {
criteriaList.add("d.DOC_CREATEDATE_D <= :createDateMax");
parameterMap.put("createDateMax", criteria.getCreateDateMax());
}
if (criteria.getTagIdList() != null && !criteria.getTagIdList().isEmpty()) {
int index = 0;
for (String tagId : criteria.getTagIdList()) {
sb.append(String.format(" join T_DOCUMENT_TAG dt%d on dt%d.DOT_IDDOCUMENT_C = d.DOC_ID_C and dt%d.DOT_IDTAG_C = :tagId%d and dt%d.DOT_DELETEDATE_D is null ", index, index, index, index, index));
parameterMap.put("tagId" + index, tagId);
index++;
}
}
if (criteria.getShared() != null && criteria.getShared()) {
criteriaList.add("(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) > 0");
}
if (criteria.getLanguage() != null) {
criteriaList.add("d.DOC_LANGUAGE_C = :language");
parameterMap.put("language", criteria.getLanguage());
}
if (criteria.getCreatorId() != null) {
criteriaList.add("d.DOC_IDUSER_C = :creatorId");
parameterMap.put("creatorId", criteria.getCreatorId());
}
criteriaList.add("d.DOC_DELETEDATE_D is null");
if (!criteriaList.isEmpty()) {
sb.append(" where ");
sb.append(Joiner.on(" and ").join(criteriaList));
}
// Perform the search
QueryParam queryParam = new QueryParam(sb.toString(), parameterMap);
List<Object[]> l = PaginatedLists.executePaginatedQuery(paginatedList, queryParam, sortCriteria);
// Assemble results
List<DocumentDto> documentDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
DocumentDto documentDto = new DocumentDto();
documentDto.setId((String) o[i++]);
documentDto.setTitle((String) o[i++]);
documentDto.setDescription((String) o[i++]);
documentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
documentDto.setLanguage((String) o[i++]);
documentDto.setShared(((Number) o[i++]).intValue() > 0);
documentDto.setFileCount(((Number) o[i]).intValue());
documentDtoList.add(documentDto);
}
paginatedList.setResultList(documentDtoList);
}
/**
* Update a document.
*
@ -301,25 +197,37 @@ public class DocumentDao {
// Get the document
Query q = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null");
q.setParameter("id", document.getId());
Document documentFromDb = (Document) q.getSingleResult();
Document documentDb = (Document) q.getSingleResult();
// Update the document
documentFromDb.setTitle(document.getTitle());
documentFromDb.setDescription(document.getDescription());
documentFromDb.setSubject(document.getSubject());
documentFromDb.setIdentifier(document.getIdentifier());
documentFromDb.setPublisher(document.getPublisher());
documentFromDb.setFormat(document.getFormat());
documentFromDb.setSource(document.getSource());
documentFromDb.setType(document.getType());
documentFromDb.setCoverage(document.getCoverage());
documentFromDb.setRights(document.getRights());
documentFromDb.setCreateDate(document.getCreateDate());
documentFromDb.setLanguage(document.getLanguage());
documentDb.setTitle(document.getTitle());
documentDb.setDescription(document.getDescription());
documentDb.setSubject(document.getSubject());
documentDb.setIdentifier(document.getIdentifier());
documentDb.setPublisher(document.getPublisher());
documentDb.setFormat(document.getFormat());
documentDb.setSource(document.getSource());
documentDb.setType(document.getType());
documentDb.setCoverage(document.getCoverage());
documentDb.setRights(document.getRights());
documentDb.setCreateDate(document.getCreateDate());
documentDb.setLanguage(document.getLanguage());
documentDb.setUpdateDate(new Date());
// Create audit log
AuditLogUtil.create(documentFromDb, AuditLogType.UPDATE, userId);
AuditLogUtil.create(documentDb, AuditLogType.UPDATE, userId);
return documentFromDb;
return documentDb;
}
/**
* Returns the number of documents.
*
* @return Number of documents
*/
public long getDocumentCount() {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query query = em.createNativeQuery("select count(d.DOC_ID_C) from T_DOCUMENT d where d.DOC_DELETEDATE_D is null");
return ((Number) query.getSingleResult()).longValue();
}
}

View File

@ -1,18 +1,17 @@
package com.sismics.docs.core.dao.jpa;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
package com.sismics.docs.core.dao;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.util.Date;
import java.util.List;
import java.util.UUID;
/**
* File DAO.
*
@ -137,13 +136,14 @@ public class FileDao {
// Get the file
Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null");
q.setParameter("id", file.getId());
File fileFromDb = (File) q.getSingleResult();
File fileDb = (File) q.getSingleResult();
// Update the file
fileFromDb.setDocumentId(file.getDocumentId());
fileFromDb.setContent(file.getContent());
fileFromDb.setOrder(file.getOrder());
fileFromDb.setMimeType(file.getMimeType());
fileDb.setDocumentId(file.getDocumentId());
fileDb.setName(file.getName());
fileDb.setContent(file.getContent());
fileDb.setOrder(file.getOrder());
fileDb.setMimeType(file.getMimeType());
return file;
}

View File

@ -1,22 +1,9 @@
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;
package com.sismics.docs.core.dao;
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.dao.criteria.GroupCriteria;
import com.sismics.docs.core.dao.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;
@ -25,6 +12,11 @@ import com.sismics.docs.core.util.jpa.QueryUtil;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.util.*;
/**
* Group DAO.
*
@ -266,16 +258,16 @@ public class GroupDao {
// 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();
Group groupDb = (Group) q.getSingleResult();
// Update the group
groupFromDb.setName(group.getName());
groupFromDb.setParentId(group.getParentId());
groupDb.setName(group.getName());
groupDb.setParentId(group.getParentId());
// Create audit log
AuditLogUtil.create(groupFromDb, AuditLogType.UPDATE, userId);
AuditLogUtil.create(groupDb, AuditLogType.UPDATE, userId);
return groupFromDb;
return groupDb;
}
}

View File

@ -0,0 +1,68 @@
package com.sismics.docs.core.dao;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.model.jpa.PasswordRecovery;
import com.sismics.util.context.ThreadLocalContext;
import org.joda.time.DateTime;
import org.joda.time.DurationFieldType;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.util.Date;
import java.util.UUID;
/**
* Password recovery DAO.
*
* @author jtremeaux
*/
public class PasswordRecoveryDao {
/**
* Create a new password recovery request.
*
* @param passwordRecovery Password recovery
* @return Unique identifier
*/
public String create(PasswordRecovery passwordRecovery) {
passwordRecovery.setId(UUID.randomUUID().toString());
passwordRecovery.setCreateDate(new Date());
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.persist(passwordRecovery);
return passwordRecovery.getId();
}
/**
* Search an active password recovery by unique identifier.
*
* @param id Unique identifier
* @return Password recovery
*/
public PasswordRecovery getActiveById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
Query q = em.createQuery("select r from PasswordRecovery r where r.id = :id and r.createDate > :createDateMin and r.deleteDate is null");
q.setParameter("id", id);
q.setParameter("createDateMin", new DateTime().withFieldAdded(DurationFieldType.hours(), -1 * Constants.PASSWORD_RECOVERY_EXPIRATION_HOUR).toDate());
return (PasswordRecovery) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes active password recovery by username.
*
* @param username Username
*/
public void deleteActiveByLogin(String username) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("update PasswordRecovery r set r.deleteDate = :deleteDate where r.username = :username and r.createDate > :createDateMin and r.deleteDate is null");
q.setParameter("username", username);
q.setParameter("deleteDate", new Date());
q.setParameter("createDateMin", new DateTime().withFieldAdded(DurationFieldType.hours(), -1 * Constants.PASSWORD_RECOVERY_EXPIRATION_HOUR).toDate());
q.executeUpdate();
}
}

View File

@ -1,17 +1,12 @@
package com.sismics.docs.core.dao.jpa;
package com.sismics.docs.core.dao;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import com.sismics.docs.core.dao.dto.RelationDto;
import com.sismics.docs.core.model.jpa.Relation;
import com.sismics.util.context.ThreadLocalContext;
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;
import java.util.*;
/**
* Relation DAO.

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa;
package com.sismics.docs.core.dao;
import com.google.common.collect.Sets;
import com.sismics.util.context.ThreadLocalContext;

View File

@ -0,0 +1,108 @@
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.criteria.RouteCriteria;
import com.sismics.docs.core.dao.dto.RouteDto;
import com.sismics.docs.core.model.jpa.Route;
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;
import javax.persistence.EntityManager;
import java.sql.Timestamp;
import java.util.*;
/**
* Route DAO.
*
* @author bgamard
*/
public class RouteDao {
/**
* Creates a new route.
*
* @param route Route
* @param userId User ID
* @return New ID
*/
public String create(Route route, String userId) {
// Create the UUID
route.setId(UUID.randomUUID().toString());
// Create the route
EntityManager em = ThreadLocalContext.get().getEntityManager();
route.setCreateDate(new Date());
em.persist(route);
// Create audit log
AuditLogUtil.create(route, AuditLogType.CREATE, userId);
return route.getId();
}
/**
* Returns the list of all routes.
*
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of routes
*/
public List<RouteDto> findByCriteria(RouteCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select r.RTE_ID_C c0, r.RTE_NAME_C c1, r.RTE_CREATEDATE_D c2");
sb.append(" from T_ROUTE r ");
// Add search criterias
if (criteria.getDocumentId() != null) {
criteriaList.add("r.RTE_IDDOCUMENT_C = :documentId");
parameterMap.put("documentId", criteria.getDocumentId());
}
criteriaList.add("r.RTE_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<RouteDto> dtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
RouteDto dto = new RouteDto();
dto.setId((String) o[i++]);
dto.setName((String) o[i++]);
dto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
dtoList.add(dto);
}
return dtoList;
}
/**
* Deletes a route and the associated steps.
*
* @param routeId Route ID
*/
public void deleteRoute(String routeId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
em.createNativeQuery("update T_ROUTE_STEP rs set RTP_DELETEDATE_D = :dateNow where rs.RTP_IDROUTE_C = :routeId and rs.RTP_DELETEDATE_D is null")
.setParameter("routeId", routeId)
.setParameter("dateNow", new Date())
.executeUpdate();
em.createNativeQuery("update T_ROUTE r set RTE_DELETEDATE_D = :dateNow where r.RTE_ID_C = :routeId and r.RTE_DELETEDATE_D is null")
.setParameter("routeId", routeId)
.setParameter("dateNow", new Date())
.executeUpdate();
}
}

View File

@ -0,0 +1,151 @@
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.criteria.RouteModelCriteria;
import com.sismics.docs.core.dao.dto.RouteModelDto;
import com.sismics.docs.core.model.jpa.RouteModel;
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;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.sql.Timestamp;
import java.util.*;
/**
* Route model DAO.
*
* @author bgamard
*/
public class RouteModelDao {
/**
* Creates a new route model.
*
* @param routeModel Route model
* @param userId User ID
* @return New ID
*/
public String create(RouteModel routeModel, String userId) {
// Create the UUID
routeModel.setId(UUID.randomUUID().toString());
// Create the route model
EntityManager em = ThreadLocalContext.get().getEntityManager();
routeModel.setCreateDate(new Date());
em.persist(routeModel);
// Create audit log
AuditLogUtil.create(routeModel, AuditLogType.CREATE, userId);
return routeModel.getId();
}
/**
* Update a route model.
*
* @param routeModel Route model to update
* @param userId User ID
* @return Updated route model
*/
public RouteModel update(RouteModel routeModel, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the route model
Query q = em.createQuery("select r from RouteModel r where r.id = :id and r.deleteDate is null");
q.setParameter("id", routeModel.getId());
RouteModel routeModelDb = (RouteModel) q.getSingleResult();
// Update the group
routeModelDb.setName(routeModel.getName());
routeModelDb.setSteps(routeModel.getSteps());
// Create audit log
AuditLogUtil.create(routeModelDb, AuditLogType.UPDATE, userId);
return routeModelDb;
}
/**
* Gets an active route model by its ID.
*
* @param id Route model ID
* @return Route model
*/
public RouteModel getActiveById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
Query q = em.createQuery("select r from RouteModel r where r.id = :id and r.deleteDate is null");
q.setParameter("id", id);
return (RouteModel) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes a route model.
*
* @param id Route model ID
* @param userId User ID
*/
public void delete(String id, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the route model
Query q = em.createQuery("select r from RouteModel r where r.id = :id and r.deleteDate is null");
q.setParameter("id", id);
RouteModel routeModelDb = (RouteModel) q.getSingleResult();
// Delete the route model
Date dateNow = new Date();
routeModelDb.setDeleteDate(dateNow);
// Create audit log
AuditLogUtil.create(routeModelDb, AuditLogType.DELETE, userId);
}
/**
* Returns the list of all route models.
*
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of route models
*/
public List<RouteModelDto> findByCriteria(RouteModelCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select rm.RTM_ID_C c0, rm.RTM_NAME_C c1, rm.RTM_CREATEDATE_D c2");
sb.append(" from T_ROUTE_MODEL rm ");
// Add search criterias
criteriaList.add("rm.RTM_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<RouteModelDto> dtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
RouteModelDto dto = new RouteModelDto();
dto.setId((String) o[i++]);
dto.setName((String) o[i++]);
dto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
dtoList.add(dto);
}
return dtoList;
}
}

View File

@ -0,0 +1,156 @@
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AclTargetType;
import com.sismics.docs.core.constant.RouteStepTransition;
import com.sismics.docs.core.constant.RouteStepType;
import com.sismics.docs.core.dao.criteria.RouteStepCriteria;
import com.sismics.docs.core.dao.dto.RouteStepDto;
import com.sismics.docs.core.model.jpa.RouteStep;
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;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.sql.Timestamp;
import java.util.*;
/**
* Route step DAO.
*
* @author bgamard
*/
public class RouteStepDao {
/**
* Creates a new route step.
*
* @param routeStep Route step
* @return New ID
*/
public String create(RouteStep routeStep) {
// Create the UUID
routeStep.setId(UUID.randomUUID().toString());
// Create the route step
EntityManager em = ThreadLocalContext.get().getEntityManager();
routeStep.setCreateDate(new Date());
em.persist(routeStep);
return routeStep.getId();
}
/**
* Get the current route step from a document.
*
* @param documentId Document ID
* @return Current route step
*/
public RouteStepDto getCurrentStep(String documentId) {
List<RouteStepDto> routeStepDtoList = findByCriteria(new RouteStepCriteria()
.setDocumentId(documentId)
.setEndDateIsNull(true), new SortCriteria(6, true));
if (routeStepDtoList.isEmpty()) {
return null;
}
return routeStepDtoList.get(0);
}
/**
* Returns the list of all route steps.
*
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of route steps
*/
public List<RouteStepDto> findByCriteria(RouteStepCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select rs.RTP_ID_C, rs.RTP_NAME_C c0, rs.RTP_TYPE_C c1, rs.RTP_TRANSITION_C c2, rs.RTP_COMMENT_C c3, rs.RTP_IDTARGET_C c4, u.USE_USERNAME_C as targetUsername, g.GRP_NAME_C, rs.RTP_ENDDATE_D c5, uv.USE_USERNAME_C as validatorUsername, rs.RTP_IDROUTE_C, rs.RTP_TRANSITIONS_C, rs.RTP_ORDER_N c6")
.append(" from T_ROUTE_STEP rs ")
.append(" join T_ROUTE r on r.RTE_ID_C = rs.RTP_IDROUTE_C ")
.append(" left join T_USER uv on uv.USE_ID_C = rs.RTP_IDVALIDATORUSER_C ")
.append(" left join T_USER u on u.USE_ID_C = rs.RTP_IDTARGET_C ")
.append(" left join T_SHARE s on s.SHA_ID_C = rs.RTP_IDTARGET_C ")
.append(" left join T_GROUP g on g.GRP_ID_C = rs.RTP_IDTARGET_C ");
// Add search criterias
if (criteria.getDocumentId() != null) {
criteriaList.add("r.RTE_IDDOCUMENT_C = :documentId");
parameterMap.put("documentId", criteria.getDocumentId());
}
if (criteria.getRouteId() != null) {
criteriaList.add("rs.RTP_IDROUTE_C = :routeId");
parameterMap.put("routeId", criteria.getRouteId());
}
if (criteria.getEndDateIsNull() != null) {
criteriaList.add("RTP_ENDDATE_D is " + (criteria.getEndDateIsNull() ? "" : "not") + " null");
}
criteriaList.add("rs.RTP_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<RouteStepDto> dtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
RouteStepDto dto = new RouteStepDto();
dto.setId((String) o[i++]);
dto.setName((String) o[i++]);
dto.setType(RouteStepType.valueOf((String) o[i++]));
dto.setTransition((String) o[i++]);
dto.setComment((String) o[i++]);
dto.setTargetId((String) o[i++]);
String userName = (String) o[i++];
String groupName = (String) o[i++];
if (userName != null) {
dto.setTargetName(userName);
dto.setTargetType(AclTargetType.USER.name());
}
if (groupName != null) {
dto.setTargetName(groupName);
dto.setTargetType(AclTargetType.GROUP.name());
}
Timestamp endDateTimestamp = (Timestamp) o[i++];
dto.setEndDateTimestamp(endDateTimestamp == null ? null : endDateTimestamp.getTime());
dto.setValidatorUserName((String) o[i++]);
dto.setRouteId((String) o[i++]);
dto.setTransitions((String) o[i]);
dtoList.add(dto);
}
return dtoList;
}
/**
* End a route step.
*
* @param id Route step ID
* @param transition Transition
* @param comment Comment
* @param validatorUserId Validator user ID
*/
public void endRouteStep(String id, RouteStepTransition transition, String comment, String validatorUserId) {
StringBuilder sb = new StringBuilder("update T_ROUTE_STEP r ");
sb.append(" set RTP_ENDDATE_D = :endDate, RTP_TRANSITION_C = :transition, RTP_COMMENT_C = :comment, RTP_IDVALIDATORUSER_C = :validatorUserId ");
sb.append(" where r.RTP_ID_C = :id");
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createNativeQuery(sb.toString());
q.setParameter("endDate", new Date());
q.setParameter("transition", transition.name());
q.setParameter("comment", comment);
q.setParameter("validatorUserId", validatorUserId);
q.setParameter("id", id);
q.executeUpdate();
}
}

View File

@ -1,14 +1,13 @@
package com.sismics.docs.core.dao.jpa;
import java.util.Date;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.Query;
package com.sismics.docs.core.dao;
import com.sismics.docs.core.model.jpa.Share;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.Date;
import java.util.UUID;
/**
* Share DAO.
*

View File

@ -1,29 +1,23 @@
package com.sismics.docs.core.dao.jpa;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
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;
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.criteria.TagCriteria;
import com.sismics.docs.core.dao.jpa.dto.TagDto;
import com.sismics.docs.core.dao.criteria.TagCriteria;
import com.sismics.docs.core.dao.dto.TagDto;
import com.sismics.docs.core.model.jpa.DocumentTag;
import com.sismics.docs.core.model.jpa.Tag;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.SecurityUtil;
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;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.util.*;
/**
* Tag DAO.
*
@ -137,6 +131,10 @@ public class TagDao {
q.setParameter("tagId", tagId);
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update Tag t set t.parentId = null where t.parentId = :tagId and t.deleteDate is null");
q.setParameter("tagId", tagId);
q.executeUpdate();
// Create audit log
AuditLogUtil.create(tagDb, AuditLogType.DELETE, userId);
@ -155,17 +153,17 @@ public class TagDao {
// Get the tag
Query q = em.createQuery("select t from Tag t where t.id = :id and t.deleteDate is null");
q.setParameter("id", tag.getId());
Tag tagFromDb = (Tag) q.getSingleResult();
Tag tagDb = (Tag) q.getSingleResult();
// Update the tag
tagFromDb.setName(tag.getName());
tagFromDb.setColor(tag.getColor());
tagFromDb.setParentId(tag.getParentId());
tagDb.setName(tag.getName());
tagDb.setColor(tag.getColor());
tagDb.setParentId(tag.getParentId());
// Create audit log
AuditLogUtil.create(tagFromDb, AuditLogType.UPDATE, userId);
AuditLogUtil.create(tagDb, AuditLogType.UPDATE, userId);
return tagFromDb;
return tagDb;
}
/**
@ -188,7 +186,7 @@ public class TagDao {
criteriaList.add("t.TAG_ID_C = :id");
parameterMap.put("id", criteria.getId());
}
if (criteria.getTargetIdList() != null) {
if (criteria.getTargetIdList() != null && !SecurityUtil.skipAclCheck(criteria.getTargetIdList())) {
sb.append(" left join T_ACL a on a.ACL_TARGETID_C in (:targetIdList) and a.ACL_SOURCEID_C = t.TAG_ID_C and a.ACL_PERM_C = 'READ' and a.ACL_DELETEDATE_D is null ");
criteriaList.add("a.ACL_ID_C is not null");
parameterMap.put("targetIdList", criteria.getTargetIdList());
@ -198,14 +196,6 @@ public class TagDao {
criteriaList.add("dt.DOT_IDDOCUMENT_C = :documentId");
parameterMap.put("documentId", criteria.getDocumentId());
}
if (criteria.getName() != null) {
criteriaList.add("t.TAG_NAME_C = :name");
parameterMap.put("name", criteria.getName());
}
if (criteria.getNameLike() != null) {
criteriaList.add("t.TAG_NAME_C like :nameLike");
parameterMap.put("nameLike", "%" + criteria.getNameLike() + "%");
}
criteriaList.add("t.TAG_DELETEDATE_D is null");

View File

@ -1,29 +1,24 @@
package com.sismics.docs.core.dao.jpa;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import org.mindrot.jbcrypt.BCrypt;
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.AuditLogType;
import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
import com.sismics.docs.core.dao.jpa.dto.UserDto;
import com.sismics.docs.core.dao.criteria.UserCriteria;
import com.sismics.docs.core.dao.dto.UserDto;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.AuditLogUtil;
import com.sismics.docs.core.util.EncryptionUtil;
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;
import org.joda.time.DateTime;
import org.mindrot.jbcrypt.BCrypt;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.sql.Timestamp;
import java.util.*;
/**
* User DAO.
@ -44,7 +39,7 @@ public class UserDao {
q.setParameter("username", username);
try {
User user = (User) q.getSingleResult();
if (!BCrypt.checkpw(password, user.getPassword())) {
if (!BCrypt.checkpw(password, user.getPassword()) || user.getDisableDate() != null) {
return null;
}
return user;
@ -59,7 +54,7 @@ public class UserDao {
* @param user User to create
* @param userId User ID
* @return User ID
* @throws Exception
* @throws Exception e
*/
public String create(User user, String userId) throws Exception {
// Create the user UUID
@ -77,6 +72,8 @@ public class UserDao {
// Create the user
user.setCreateDate(new Date());
user.setPassword(hashPassword(user.getPassword()));
user.setPrivateKey(EncryptionUtil.generatePrivateKey());
user.setStorageCurrent(0L);
em.persist(user);
// Create audit log
@ -98,16 +95,17 @@ public class UserDao {
// Get the user
Query q = em.createQuery("select u from User u where u.id = :id and u.deleteDate is null");
q.setParameter("id", user.getId());
User userFromDb = (User) q.getSingleResult();
User userDb = (User) q.getSingleResult();
// Update the user (except password)
userFromDb.setEmail(user.getEmail());
userFromDb.setStorageQuota(user.getStorageQuota());
userFromDb.setStorageCurrent(user.getStorageCurrent());
userFromDb.setTotpKey(user.getTotpKey());
userDb.setEmail(user.getEmail());
userDb.setStorageQuota(user.getStorageQuota());
userDb.setStorageCurrent(user.getStorageCurrent());
userDb.setTotpKey(user.getTotpKey());
userDb.setDisableDate(user.getDisableDate());
// Create audit log
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE, userId);
AuditLogUtil.create(userDb, AuditLogType.UPDATE, userId);
return user;
}
@ -116,20 +114,17 @@ public class UserDao {
* Updates a user's quota.
*
* @param user User to update
* @return Updated user
*/
public User updateQuota(User user) {
public void updateQuota(User user) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user
Query q = em.createQuery("select u from User u where u.id = :id and u.deleteDate is null");
q.setParameter("id", user.getId());
User userFromDb = (User) q.getSingleResult();
User userDb = (User) q.getSingleResult();
// Update the user
userFromDb.setStorageQuota(user.getStorageQuota());
return user;
userDb.setStorageCurrent(user.getStorageCurrent());
}
/**
@ -145,17 +140,37 @@ public class UserDao {
// Get the user
Query q = em.createQuery("select u from User u where u.id = :id and u.deleteDate is null");
q.setParameter("id", user.getId());
User userFromDb = (User) q.getSingleResult();
User userDb = (User) q.getSingleResult();
// Update the user
userFromDb.setPassword(hashPassword(user.getPassword()));
userDb.setPassword(hashPassword(user.getPassword()));
// Create audit log
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE, userId);
AuditLogUtil.create(userDb, AuditLogType.UPDATE, userId);
return user;
}
/**
* Update the hashed password silently.
*
* @param user User to update
* @return Updated user
*/
public User updateHashedPassword(User user) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the user
Query q = em.createQuery("select u from User u where u.id = :id and u.deleteDate is null");
q.setParameter("id", user.getId());
User userDb = (User) q.getSingleResult();
// Update the user
userDb.setPassword(user.getPassword());
return user;
}
/**
* Gets a user by its ID.
*
@ -200,39 +215,39 @@ public class UserDao {
// Get the user
Query q = em.createQuery("select u from User u where u.username = :username and u.deleteDate is null");
q.setParameter("username", username);
User userFromDb = (User) q.getSingleResult();
User userDb = (User) q.getSingleResult();
// Delete the user
Date dateNow = new Date();
userFromDb.setDeleteDate(dateNow);
userDb.setDeleteDate(dateNow);
// Delete linked data
q = em.createQuery("delete from AuthenticationToken at where at.userId = :userId");
q.setParameter("userId", userFromDb.getId());
q.setParameter("userId", userDb.getId());
q.executeUpdate();
q = em.createQuery("update Document d set d.deleteDate = :dateNow where d.userId = :userId and d.deleteDate is null");
q.setParameter("userId", userFromDb.getId());
q.setParameter("userId", userDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update File f set f.deleteDate = :dateNow where f.userId = :userId and f.deleteDate is null");
q.setParameter("userId", userFromDb.getId());
q.setParameter("userId", userDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.targetId = :userId and a.deleteDate is null");
q.setParameter("userId", userFromDb.getId());
q.setParameter("userId", userDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
q = em.createQuery("update Comment c set c.deleteDate = :dateNow where c.userId = :userId and c.deleteDate is null");
q.setParameter("userId", userFromDb.getId());
q.setParameter("userId", userDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
// Create audit log
AuditLogUtil.create(userFromDb, AuditLogType.DELETE, userId);
AuditLogUtil.create(userDb, AuditLogType.DELETE, userId);
}
/**
@ -256,7 +271,7 @@ public class UserDao {
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3, u.USE_STORAGECURRENT_N as c4, u.USE_STORAGEQUOTA_N as c5");
StringBuilder sb = new StringBuilder("select u.USE_ID_C as c0, u.USE_USERNAME_C as c1, u.USE_EMAIL_C as c2, u.USE_CREATEDATE_D as c3, u.USE_STORAGECURRENT_N as c4, u.USE_STORAGEQUOTA_N as c5, u.USE_TOTPKEY_C as c6, u.USE_DISABLEDATE_D as c7");
sb.append(" from T_USER u ");
// Add search criterias
@ -264,7 +279,14 @@ public class UserDao {
criteriaList.add("lower(u.USE_USERNAME_C) like lower(:search)");
parameterMap.put("search", "%" + criteria.getSearch() + "%");
}
if (criteria.getUserId() != null) {
criteriaList.add("u.USE_ID_C = :userId");
parameterMap.put("userId", criteria.getUserId());
}
if (criteria.getUserName() != null) {
criteriaList.add("u.USE_USERNAME_C = :userName");
parameterMap.put("userName", criteria.getUserName());
}
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());
@ -292,9 +314,39 @@ public class UserDao {
userDto.setEmail((String) o[i++]);
userDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
userDto.setStorageCurrent(((Number) o[i++]).longValue());
userDto.setStorageQuota(((Number) o[i]).longValue());
userDto.setStorageQuota(((Number) o[i++]).longValue());
userDto.setTotpKey((String) o[i++]);
if (o[i] != null) {
userDto.setDisableTimestamp(((Timestamp) o[i]).getTime());
}
userDtoList.add(userDto);
}
return userDtoList;
}
/**
* Returns the global storage used by all users.
*
* @return Current global storage
*/
public long getGlobalStorageCurrent() {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query query = em.createNativeQuery("select sum(u.USE_STORAGECURRENT_N) from T_USER u where u.USE_DELETEDATE_D is null");
return ((Number) query.getSingleResult()).longValue();
}
/**
* Returns the number of active users.
*
* @return Number of active users
*/
public long getActiveUserCount() {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query query = em.createNativeQuery("select count(u.USE_ID_C) from T_USER u where u.USE_DELETEDATE_D is null and (u.USE_DISABLEDATE_D is null or u.USE_DISABLEDATE_D >= :fromDate and u.USE_DISABLEDATE_D < :toDate)");
DateTime fromDate = DateTime.now().minusMonths(1).dayOfMonth().withMinimumValue().withTimeAtStartOfDay();
DateTime toDate = fromDate.plusMonths(1);
query.setParameter("fromDate", fromDate.toDate());
query.setParameter("toDate", toDate.toDate());
return ((Number) query.getSingleResult()).longValue();
}
}

View File

@ -1,14 +1,13 @@
package com.sismics.docs.core.dao.jpa;
package com.sismics.docs.core.dao;
import java.util.List;
import java.util.UUID;
import com.sismics.docs.core.model.jpa.Vocabulary;
import com.sismics.util.context.ThreadLocalContext;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.sismics.docs.core.model.jpa.Vocabulary;
import com.sismics.util.context.ThreadLocalContext;
import java.util.List;
import java.util.UUID;
/**
* Vocabulary DAO.
@ -76,14 +75,14 @@ public class VocabularyDao {
// Get the vocabulary entry
Query q = em.createQuery("select v from Vocabulary v where v.id = :id");
q.setParameter("id", vocabulary.getId());
Vocabulary vocabularyFromDb = (Vocabulary) q.getSingleResult();
Vocabulary vocabularyDb = (Vocabulary) q.getSingleResult();
// Update the vocabulary entry
vocabularyFromDb.setName(vocabulary.getName());
vocabularyFromDb.setValue(vocabulary.getValue());
vocabularyFromDb.setOrder(vocabulary.getOrder());
vocabularyDb.setName(vocabulary.getName());
vocabularyDb.setValue(vocabulary.getValue());
vocabularyDb.setOrder(vocabulary.getOrder());
return vocabularyFromDb;
return vocabularyDb;
}
/**

View File

@ -0,0 +1,123 @@
package com.sismics.docs.core.dao;
import com.google.common.base.Joiner;
import com.sismics.docs.core.dao.criteria.WebhookCriteria;
import com.sismics.docs.core.dao.dto.WebhookDto;
import com.sismics.docs.core.model.jpa.Webhook;
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;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import java.sql.Timestamp;
import java.util.*;
/**
* Webhook DAO.
*
* @author bgamard
*/
public class WebhookDao {
/**
* Returns the list of all webhooks.
*
* @param criteria Search criteria
* @param sortCriteria Sort criteria
* @return List of webhooks
*/
public List<WebhookDto> findByCriteria(WebhookCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<>();
List<String> criteriaList = new ArrayList<>();
StringBuilder sb = new StringBuilder("select w.WHK_ID_C as c0, w.WHK_EVENT_C as c1, w.WHK_URL_C as c2, w.WHK_CREATEDATE_D as c3 ");
sb.append(" from T_WEBHOOK w ");
// Add search criterias
if (criteria.getEvent() != null) {
criteriaList.add("w.WHK_EVENT_C = :event");
parameterMap.put("event", criteria.getEvent().name());
}
criteriaList.add("w.WHK_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<WebhookDto> webhookDtoList = new ArrayList<>();
for (Object[] o : l) {
int i = 0;
WebhookDto webhookDto = new WebhookDto()
.setId((String) o[i++])
.setEvent((String) o[i++])
.setUrl((String) o[i++])
.setCreateTimestamp(((Timestamp) o[i]).getTime());
webhookDtoList.add(webhookDto);
}
return webhookDtoList;
}
/**
* Returns a webhook by ID.
*
* @param id Webhook ID
* @return Webhook
*/
public Webhook getActiveById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select w from Webhook w where w.id = :id and w.deleteDate is null");
q.setParameter("id", id);
try {
return (Webhook) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Creates a new webhook.
*
* @param webhook Webhook
* @return New ID
*/
public String create(Webhook webhook) {
// Create the UUID
webhook.setId(UUID.randomUUID().toString());
// Create the webhook
EntityManager em = ThreadLocalContext.get().getEntityManager();
webhook.setCreateDate(new Date());
em.persist(webhook);
return webhook.getId();
}
/**
* Deletes a webhook.
*
* @param webhookId Webhook ID
*/
public void delete(String webhookId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the group
Query q = em.createQuery("select w from Webhook w where w.id = :id and w.deleteDate is null");
q.setParameter("id", webhookId);
Webhook webhookDb = (Webhook) q.getSingleResult();
// Delete the group
Date dateNow = new Date();
webhookDb.setDeleteDate(dateNow);
}
}

View File

@ -1,5 +1,4 @@
package com.sismics.docs.core.dao.jpa.criteria;
package com.sismics.docs.core.dao.criteria;
/**

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.criteria;
package com.sismics.docs.core.dao.criteria;
import java.util.Date;
import java.util.List;
@ -36,9 +36,20 @@ public class DocumentCriteria {
private Date createDateMax;
/**
* Tag IDs.
* Minimum update date.
*/
private List<String> tagIdList;
private Date updateDateMin;
/**
* Maximum update date.
*/
private Date updateDateMax;
/**
* Tag IDs.
* The first level list will be AND'ed and the second level list will be OR'ed.
*/
private List<List<String>> tagIdList;
/**
* Shared status.
@ -54,6 +65,11 @@ public class DocumentCriteria {
* Creator ID.
*/
private String creatorId;
/**
* A route is active.
*/
private Boolean activeRoute;
public List<String> getTargetIdList() {
return targetIdList;
@ -95,11 +111,11 @@ public class DocumentCriteria {
this.createDateMax = createDateMax;
}
public List<String> getTagIdList() {
public List<List<String>> getTagIdList() {
return tagIdList;
}
public void setTagIdList(List<String> tagIdList) {
public void setTagIdList(List<List<String>> tagIdList) {
this.tagIdList = tagIdList;
}
@ -126,4 +142,28 @@ public class DocumentCriteria {
public void setCreatorId(String creatorId) {
this.creatorId = creatorId;
}
public Boolean getActiveRoute() {
return activeRoute;
}
public Date getUpdateDateMin() {
return updateDateMin;
}
public void setUpdateDateMin(Date updateDateMin) {
this.updateDateMin = updateDateMin;
}
public Date getUpdateDateMax() {
return updateDateMax;
}
public void setUpdateDateMax(Date updateDateMax) {
this.updateDateMax = updateDateMax;
}
public void setActiveRoute(Boolean activeRoute) {
this.activeRoute = activeRoute;
}
}

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.criteria;
package com.sismics.docs.core.dao.criteria;
/**
* Group criteria.

View File

@ -0,0 +1,23 @@
package com.sismics.docs.core.dao.criteria;
/**
* Route criteria.
*
* @author bgamard
*/
public class RouteCriteria {
/**
* Document ID.
*/
private String documentId;
public String getDocumentId() {
return documentId;
}
public RouteCriteria setDocumentId(String documentId) {
this.documentId = documentId;
return this;
}
}

View File

@ -0,0 +1,10 @@
package com.sismics.docs.core.dao.criteria;
/**
* Route model criteria.
*
* @author bgamard
*/
public class RouteModelCriteria {
}

View File

@ -0,0 +1,51 @@
package com.sismics.docs.core.dao.criteria;
/**
* Route step criteria.
*
* @author bgamard
*/
public class RouteStepCriteria {
/**
* Document ID.
*/
private String documentId;
/**
* Route ID.
*/
private String routeId;
/**
* End date is null.
*/
private Boolean endDateIsNull;
public String getDocumentId() {
return documentId;
}
public RouteStepCriteria setDocumentId(String documentId) {
this.documentId = documentId;
return this;
}
public String getRouteId() {
return routeId;
}
public RouteStepCriteria setRouteId(String routeId) {
this.routeId = routeId;
return this;
}
public Boolean getEndDateIsNull() {
return endDateIsNull;
}
public RouteStepCriteria setEndDateIsNull(Boolean endDateIsNull) {
this.endDateIsNull = endDateIsNull;
return this;
}
}

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.criteria;
package com.sismics.docs.core.dao.criteria;
import java.util.List;
@ -28,11 +28,6 @@ public class TagCriteria {
*/
private String name;
/**
* Approximate tag name.
*/
private String nameLike;
public String getId() {
return id;
}
@ -59,22 +54,4 @@ public class TagCriteria {
this.documentId = documentId;
return this;
}
public String getName() {
return name;
}
public TagCriteria setName(String name) {
this.name = name;
return this;
}
public String getNameLike() {
return nameLike;
}
public TagCriteria setNameLike(String nameLike) {
this.nameLike = nameLike;
return this;
}
}

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.criteria;
package com.sismics.docs.core.dao.criteria;
/**
* User criteria.
@ -16,6 +16,16 @@ public class UserCriteria {
*/
private String groupId;
/**
* User ID.
*/
private String userId;
/**
* Username.
*/
private String userName;
public String getSearch() {
return search;
}
@ -33,4 +43,22 @@ public class UserCriteria {
this.groupId = groupId;
return this;
}
public String getUserId() {
return userId;
}
public UserCriteria setUserId(String userId) {
this.userId = userId;
return this;
}
public String getUserName() {
return userName;
}
public UserCriteria setUserName(String userName) {
this.userName = userName;
return this;
}
}

View File

@ -0,0 +1,24 @@
package com.sismics.docs.core.dao.criteria;
import com.sismics.docs.core.constant.WebhookEvent;
/**
* Webhook criteria.
*
* @author bgamard
*/
public class WebhookCriteria {
/**
* Webhook event.
*/
private WebhookEvent event;
public WebhookEvent getEvent() {
return event;
}
public WebhookCriteria setEvent(WebhookEvent event) {
this.event = event;
return this;
}
}

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.dto;
package com.sismics.docs.core.dao.dto;
import com.sismics.docs.core.constant.PermType;

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.dto;
package com.sismics.docs.core.dao.dto;
import com.sismics.docs.core.constant.AuditLogType;

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.dto;
package com.sismics.docs.core.dao.dto;
/**
* Comment DTO.

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.dto;
package com.sismics.docs.core.dao.dto;
/**
* Contributor DTO.

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.dto;
package com.sismics.docs.core.dao.dto;
/**
* Document DTO.
@ -71,6 +71,11 @@ public class DocumentDto {
*/
private Long createTimestamp;
/**
* Update date.
*/
private Long updateTimestamp;
/**
* Shared status.
*/
@ -85,7 +90,22 @@ public class DocumentDto {
* Document creator.
*/
private String creator;
/**
* A route is active.
*/
private boolean activeRoute;
/**
* Current route step name.
*/
private String currentStepName;
/**
* Search highlight.
*/
private String highlight;
public String getId() {
return id;
}
@ -213,4 +233,38 @@ public class DocumentDto {
public void setCreator(String creator) {
this.creator = creator;
}
public boolean isActiveRoute() {
return activeRoute;
}
public void setActiveRoute(boolean activeRoute) {
this.activeRoute = activeRoute;
}
public String getCurrentStepName() {
return currentStepName;
}
public Long getUpdateTimestamp() {
return updateTimestamp;
}
public void setUpdateTimestamp(Long updateTimestamp) {
this.updateTimestamp = updateTimestamp;
}
public DocumentDto setCurrentStepName(String currentStepName) {
this.currentStepName = currentStepName;
return this;
}
public String getHighlight() {
return highlight;
}
public DocumentDto setHighlight(String highlight) {
this.highlight = highlight;
return this;
}
}

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.dto;
package com.sismics.docs.core.dao.dto;
/**
* Group DTO.

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.dto;
package com.sismics.docs.core.dao.dto;
/**
* Tag DTO.

View File

@ -0,0 +1,50 @@
package com.sismics.docs.core.dao.dto;
/**
* Route DTO.
*
* @author bgamard
*/
public class RouteDto {
/**
* Route ID.
*/
private String id;
/**
* Name.
*/
private String name;
/**
* Creation date.
*/
private Long createTimestamp;
public String getId() {
return id;
}
public RouteDto setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public RouteDto setName(String name) {
this.name = name;
return this;
}
public Long getCreateTimestamp() {
return createTimestamp;
}
public RouteDto setCreateTimestamp(Long createTimestamp) {
this.createTimestamp = createTimestamp;
return this;
}
}

View File

@ -0,0 +1,50 @@
package com.sismics.docs.core.dao.dto;
/**
* Route model DTO.
*
* @author bgamard
*/
public class RouteModelDto {
/**
* Route model ID.
*/
private String id;
/**
* Name.
*/
private String name;
/**
* Creation date.
*/
private Long createTimestamp;
public String getId() {
return id;
}
public RouteModelDto setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public RouteModelDto setName(String name) {
this.name = name;
return this;
}
public Long getCreateTimestamp() {
return createTimestamp;
}
public RouteModelDto setCreateTimestamp(Long createTimestamp) {
this.createTimestamp = createTimestamp;
return this;
}
}

View File

@ -0,0 +1,201 @@
package com.sismics.docs.core.dao.dto;
import com.sismics.docs.core.constant.RouteStepType;
import com.sismics.util.JsonUtil;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
/**
* Route step DTO.
*
* @author bgamard
*/
public class RouteStepDto {
/**
* Route step ID.
*/
private String id;
/**
* Name.
*/
private String name;
/**
* Type.
*/
private RouteStepType type;
/**
* Transition.
*/
private String transition;
/**
* Comment.
*/
private String comment;
/**
* Target ID (user or group).
*/
private String targetId;
/**
* Target name.
*/
private String targetName;
/**
* Target type.
*/
private String targetType;
/**
* End date.
*/
private Long endDateTimestamp;
/**
* Validator's username.
*/
private String validatorUserName;
/**
* Transitions data.
*/
private String transitions;
/**
* Route ID.
*/
private String routeId;
public String getId() {
return id;
}
public RouteStepDto setId(String id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public RouteStepDto setName(String name) {
this.name = name;
return this;
}
public RouteStepType getType() {
return type;
}
public RouteStepDto setType(RouteStepType type) {
this.type = type;
return this;
}
public String getTransition() {
return transition;
}
public RouteStepDto setTransition(String transition) {
this.transition = transition;
return this;
}
public String getComment() {
return comment;
}
public RouteStepDto setComment(String comment) {
this.comment = comment;
return this;
}
public String getTargetId() {
return targetId;
}
public RouteStepDto setTargetId(String targetId) {
this.targetId = targetId;
return this;
}
public String getTargetName() {
return targetName;
}
public RouteStepDto setTargetName(String targetName) {
this.targetName = targetName;
return this;
}
public String getTargetType() {
return targetType;
}
public RouteStepDto setTargetType(String targetType) {
this.targetType = targetType;
return this;
}
public Long getEndDateTimestamp() {
return endDateTimestamp;
}
public RouteStepDto setEndDateTimestamp(Long endDateTimestamp) {
this.endDateTimestamp = endDateTimestamp;
return this;
}
public String getValidatorUserName() {
return validatorUserName;
}
public RouteStepDto setValidatorUserName(String validatorUserName) {
this.validatorUserName = validatorUserName;
return this;
}
public String getRouteId() {
return routeId;
}
public RouteStepDto setRouteId(String routeId) {
this.routeId = routeId;
return this;
}
public String getTransitions() {
return transitions;
}
public RouteStepDto setTransitions(String transitions) {
this.transitions = transitions;
return this;
}
/**
* Transform in JSON.
*
* @return JSON object builder
*/
public JsonObjectBuilder toJson() {
return Json.createObjectBuilder()
.add("name", getName())
.add("type", getType().name())
.add("comment", JsonUtil.nullable(getComment()))
.add("end_date", JsonUtil.nullable(getEndDateTimestamp()))
.add("validator_username", JsonUtil.nullable(getValidatorUserName()))
.add("target", Json.createObjectBuilder()
.add("id", getTargetId())
.add("name", JsonUtil.nullable(getTargetName()))
.add("type", getTargetType()))
.add("transition", JsonUtil.nullable(getTransition()));
}
}

View File

@ -1,4 +1,4 @@
package com.sismics.docs.core.dao.jpa.dto;
package com.sismics.docs.core.dao.dto;
/**
* Tag DTO.

View File

@ -1,4 +1,6 @@
package com.sismics.docs.core.dao.jpa.dto;
package com.sismics.docs.core.dao.dto;
import com.google.common.base.MoreObjects;
/**
* User DTO.
@ -26,6 +28,11 @@ public class UserDto {
*/
private Long createTimestamp;
/**
* Disable date of this user.
*/
private Long disableTimestamp;
/**
* Storage quota.
*/
@ -35,7 +42,12 @@ public class UserDto {
* Storage current usage.
*/
private Long storageCurrent;
/**
* TOTP key.
*/
private String totpKey;
public String getId() {
return id;
}
@ -67,7 +79,16 @@ public class UserDto {
public void setCreateTimestamp(Long createTimestamp) {
this.createTimestamp = createTimestamp;
}
public Long getDisableTimestamp() {
return disableTimestamp;
}
public UserDto setDisableTimestamp(Long disableTimestamp) {
this.disableTimestamp = disableTimestamp;
return this;
}
public Long getStorageQuota() {
return storageQuota;
}
@ -83,4 +104,21 @@ public class UserDto {
public void setStorageCurrent(Long storageCurrent) {
this.storageCurrent = storageCurrent;
}
public String getTotpKey() {
return totpKey;
}
public void setTotpKey(String totpKey) {
this.totpKey = totpKey;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("username", username)
.add("email", email)
.toString();
}
}

View File

@ -0,0 +1,74 @@
package com.sismics.docs.core.dao.dto;
/**
* Webhook DTO.
*
* @author bgamard
*/
public class WebhookDto {
/**
* Webhook ID.
*/
private String id;
/**
* Event.
*/
private String event;
/**
* URL.
*/
private String url;
/**
* Creation date.
*/
private Long createTimestamp;
public String getId() {
return id;
}
public WebhookDto setId(String id) {
this.id = id;
return this;
}
public String getEvent() {
return event;
}
public WebhookDto setEvent(String event) {
this.event = event;
return this;
}
public String getUrl() {
return url;
}
public WebhookDto setUrl(String url) {
this.url = url;
return this;
}
public Long getCreateTimestamp() {
return createTimestamp;
}
public WebhookDto setCreateTimestamp(Long createTimestamp) {
this.createTimestamp = createTimestamp;
return this;
}
@Override
public boolean equals(Object obj) {
return id.equals(((WebhookDto) obj).getId());
}
@Override
public int hashCode() {
return id.hashCode();
}
}

View File

@ -1,30 +0,0 @@
package com.sismics.docs.core.dao.jpa.dto;
/**
* Tag stat DTO.
*
* @author bgamard
*/
public class TagStatDto extends TagDto {
private int count;
/**
* Getter of count.
*
* @return the count
*/
public int getCount() {
return count;
}
/**
* Setter of count.
*
* @param count count
*/
public void setCount(int count) {
this.count = count;
}
}

View File

@ -1,238 +0,0 @@
package com.sismics.docs.core.dao.lucene;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
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.util.LuceneUtil;
import com.sismics.docs.core.util.LuceneUtil.LuceneRunnable;
/**
* Lucene DAO.
*
* @author bgamard
*/
public class LuceneDao {
/**
* Destroy and rebuild index.
*
* @param fileList List of files
*/
public void rebuildIndex(final List<Document> documentList, final List<File> fileList) {
LuceneUtil.handle(new LuceneRunnable() {
@Override
public void run(IndexWriter indexWriter) throws Exception {
// Empty index
indexWriter.deleteAll();
// Add all documents
for (Document document : documentList) {
org.apache.lucene.document.Document luceneDocument = getDocumentFromDocument(document);
indexWriter.addDocument(luceneDocument);
}
// Add all files
for (File file : fileList) {
org.apache.lucene.document.Document luceneDocument = getDocumentFromFile(file);
indexWriter.addDocument(luceneDocument);
}
}
});
}
/**
* Add document to the index.
*
* @param document Document to add
*/
public void createDocument(final Document document) {
LuceneUtil.handle(new LuceneRunnable() {
@Override
public void run(IndexWriter indexWriter) throws Exception {
org.apache.lucene.document.Document luceneDocument = getDocumentFromDocument(document);
indexWriter.addDocument(luceneDocument);
}
});
}
/**
* Add file to the index.
*
* @param file File to add
*/
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);
indexWriter.addDocument(luceneDocument);
}
});
}
/**
* Update document index.
*
* @param document Updated document
*/
public void updateDocument(final Document document) {
LuceneUtil.handle(new LuceneRunnable() {
@Override
public void run(IndexWriter indexWriter) throws Exception {
org.apache.lucene.document.Document luceneDocument = getDocumentFromDocument(document);
indexWriter.updateDocument(new Term("id", document.getId()), luceneDocument);
}
});
}
/**
* Delete document from the index.
*
* @param id Document ID to delete
*/
public void deleteDocument(final String id) {
LuceneUtil.handle(new LuceneRunnable() {
@Override
public void run(IndexWriter indexWriter) throws Exception {
indexWriter.deleteDocuments(new Term("id", id));
}
});
}
/**
* Search files.
*
* @param searchQuery Search query on title and description
* @param fullSearchQuery Search query on all fields
* @return List of document IDs
* @throws Exception
*/
public Set<String> search(String searchQuery, String fullSearchQuery) throws Exception {
// Escape query and add quotes so QueryParser generate a PhraseQuery
searchQuery = "\"" + QueryParserUtil.escape(searchQuery + " " + fullSearchQuery) + "\"";
fullSearchQuery = "\"" + QueryParserUtil.escape(fullSearchQuery) + "\"";
// Build search query
StandardQueryParser qpHelper = new StandardQueryParser(new StandardAnalyzer());
qpHelper.setPhraseSlop(100000); // PhraseQuery add terms
// Search on documents and files
BooleanQuery query = new BooleanQuery.Builder()
.add(qpHelper.parse(searchQuery, "title"), Occur.SHOULD)
.add(qpHelper.parse(searchQuery, "description"), Occur.SHOULD)
.add(qpHelper.parse(searchQuery, "subject"), Occur.SHOULD)
.add(qpHelper.parse(searchQuery, "identifier"), Occur.SHOULD)
.add(qpHelper.parse(searchQuery, "publisher"), Occur.SHOULD)
.add(qpHelper.parse(searchQuery, "format"), Occur.SHOULD)
.add(qpHelper.parse(searchQuery, "source"), Occur.SHOULD)
.add(qpHelper.parse(searchQuery, "type"), Occur.SHOULD)
.add(qpHelper.parse(searchQuery, "coverage"), Occur.SHOULD)
.add(qpHelper.parse(searchQuery, "rights"), Occur.SHOULD)
.add(qpHelper.parse(fullSearchQuery, "content"), Occur.SHOULD)
.build();
// Search
DirectoryReader directoryReader = AppContext.getInstance().getIndexingService().getDirectoryReader();
Set<String> documentIdList = new HashSet<>();
if (directoryReader == null) {
// The directory reader is not yet initialized (probably because there is nothing indexed)
return documentIdList;
}
IndexSearcher searcher = new IndexSearcher(directoryReader);
TopDocs topDocs = searcher.search(query, Integer.MAX_VALUE);
ScoreDoc[] docs = topDocs.scoreDocs;
// Extract document IDs
for (ScoreDoc doc : docs) {
org.apache.lucene.document.Document document = searcher.doc(doc.doc);
String type = document.get("doctype");
String documentId = null;
if (type.equals("document")) {
documentId = document.get("id");
} else if (type.equals("file")) {
documentId = document.get("document_id");
}
documentIdList.add(documentId);
}
return documentIdList;
}
/**
* Build Lucene document from database document.
*
* @param document Document
* @return Document
*/
private org.apache.lucene.document.Document getDocumentFromDocument(Document document) {
org.apache.lucene.document.Document luceneDocument = new org.apache.lucene.document.Document();
luceneDocument.add(new StringField("id", document.getId(), Field.Store.YES));
luceneDocument.add(new StringField("doctype", "document", Field.Store.YES));
luceneDocument.add(new TextField("title", document.getTitle(), Field.Store.NO));
if (document.getDescription() != null) {
luceneDocument.add(new TextField("description", document.getDescription(), Field.Store.NO));
}
if (document.getSubject() != null) {
luceneDocument.add(new TextField("subject", document.getSubject(), Field.Store.NO));
}
if (document.getIdentifier() != null) {
luceneDocument.add(new TextField("identifier", document.getIdentifier(), Field.Store.NO));
}
if (document.getPublisher() != null) {
luceneDocument.add(new TextField("publisher", document.getPublisher(), Field.Store.NO));
}
if (document.getFormat() != null) {
luceneDocument.add(new TextField("format", document.getFormat(), Field.Store.NO));
}
if (document.getSource() != null) {
luceneDocument.add(new TextField("source", document.getSource(), Field.Store.NO));
}
if (document.getType() != null) {
luceneDocument.add(new TextField("type", document.getType(), Field.Store.NO));
}
if (document.getCoverage() != null) {
luceneDocument.add(new TextField("coverage", document.getCoverage(), Field.Store.NO));
}
if (document.getRights() != null) {
luceneDocument.add(new TextField("rights", document.getRights(), Field.Store.NO));
}
return luceneDocument;
}
/**
* Build Lucene document from file.
*
* @param file File
* @return 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));
luceneDocument.add(new StringField("document_id", file.getDocumentId(), Field.Store.YES));
if (file.getContent() != null) {
luceneDocument.add(new TextField("content", file.getContent(), Field.Store.NO));
}
return luceneDocument;
}
}

View File

@ -0,0 +1,9 @@
package com.sismics.docs.core.event;
/**
* ACL created event.
*
* @author bgamard
*/
public class AclCreatedAsyncEvent extends AclEvent {
}

View File

@ -0,0 +1,9 @@
package com.sismics.docs.core.event;
/**
* ACL deleted event.
*
* @author bgamard
*/
public class AclDeletedAsyncEvent extends AclEvent {
}

View File

@ -0,0 +1,62 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.constant.PermType;
/**
* ACL event.
*
* @author bgamard
*/
public abstract class AclEvent extends UserEvent {
/**
* Source ID.
*/
private String sourceId;
/**
* Permission type.
*/
private PermType perm;
/**
* Target ID.
*/
private String targetId;
public String getSourceId() {
return sourceId;
}
public AclEvent setSourceId(String sourceId) {
this.sourceId = sourceId;
return this;
}
public PermType getPerm() {
return perm;
}
public AclEvent setPerm(PermType permType) {
this.perm = permType;
return this;
}
public String getTargetId() {
return targetId;
}
public AclEvent setTargetId(String targetId) {
this.targetId = targetId;
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("sourceId", sourceId)
.add("perm", perm)
.add("targetId", targetId)
.toString();
}
}

View File

@ -1,75 +1,9 @@
package com.sismics.docs.core.event;
import java.io.InputStream;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.File;
/**
* New file created event.
*
* @author bgamard
*/
public class FileCreatedAsyncEvent extends UserEvent {
/**
* Created file.
*/
private File file;
/**
* Language of the file.
*/
private String language;
/**
* Unencrypted input stream containing the file.
*/
private InputStream inputStream;
/**
* Unencrypted input stream containing a PDF representation
* of the file. May be null if the PDF conversion is not
* necessary or not possible.
*/
private InputStream pdfInputStream;
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public InputStream getPdfInputStream() {
return pdfInputStream;
}
public void setPdfInputStream(InputStream pdfInputStream) {
this.pdfInputStream = pdfInputStream;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("file", file)
.add("language", language)
.toString();
}
public class FileCreatedAsyncEvent extends FileEvent {
}

View File

@ -0,0 +1,61 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.model.jpa.File;
import java.nio.file.Path;
/**
* New file event.
*
* @author bgamard
*/
public abstract class FileEvent extends UserEvent {
/**
* Created file.
*/
private File file;
/**
* Language of the file.
*/
private String language;
/**
* Unencrypted original file.
*/
private Path unencryptedFile;
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public Path getUnencryptedFile() {
return unencryptedFile;
}
public FileEvent setUnencryptedFile(Path unencryptedFile) {
this.unencryptedFile = unencryptedFile;
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("file", file)
.add("language", language)
.toString();
}
}

View File

@ -0,0 +1,9 @@
package com.sismics.docs.core.event;
/**
* New file created event.
*
* @author bgamard
*/
public class FileUpdatedAsyncEvent extends FileEvent {
}

View File

@ -0,0 +1,46 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.dao.dto.UserDto;
import com.sismics.docs.core.model.jpa.PasswordRecovery;
/**
* Event fired on user's password lost event.
*
* @author jtremeaux
*/
public class PasswordLostEvent {
/**
* User.
*/
private UserDto user;
/**
* Password recovery request.
*/
private PasswordRecovery passwordRecovery;
public UserDto getUser() {
return user;
}
public void setUser(UserDto user) {
this.user = user;
}
public PasswordRecovery getPasswordRecovery() {
return passwordRecovery;
}
public void setPasswordRecovery(PasswordRecovery passwordRecovery) {
this.passwordRecovery = passwordRecovery;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("user", user)
.add("passwordRecovery", "**hidden**")
.toString();
}
}

View File

@ -0,0 +1,47 @@
package com.sismics.docs.core.event;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.dao.dto.UserDto;
import com.sismics.docs.core.model.jpa.Document;
/**
* Event fired on route step validation event.
*
* @author bgamard
*/
public class RouteStepValidateEvent {
/**
* User.
*/
private UserDto user;
/**
* Document linked to the route.
*/
private Document document;
public UserDto getUser() {
return user;
}
public void setUser(UserDto user) {
this.user = user;
}
public Document getDocument() {
return document;
}
public RouteStepValidateEvent setDocument(Document document) {
this.document = document;
return this;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("user", user)
.add("document", document)
.toString();
}
}

View File

@ -0,0 +1,37 @@
package com.sismics.docs.core.listener.async;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.event.AclCreatedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.util.TransactionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Listener on ACL created.
*
* @author bgamard
*/
public class AclCreatedAsyncListener {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(AclCreatedAsyncListener.class);
/**
* ACL created.
*
* @param event ACL created event
*/
@Subscribe
@AllowConcurrentEvents
public void on(final AclCreatedAsyncEvent event) {
if (log.isInfoEnabled()) {
log.info("ACL created event: " + event.toString());
}
TransactionUtil.handle(() -> AppContext.getInstance().getIndexingHandler()
.createAcl(event.getSourceId(), event.getPerm(), event.getTargetId()));
}
}

View File

@ -0,0 +1,37 @@
package com.sismics.docs.core.listener.async;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.event.AclDeletedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.util.TransactionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Listener on ACL deleted.
*
* @author bgamard
*/
public class AclDeletedAsyncListener {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(AclDeletedAsyncListener.class);
/**
* ACL deleted.
*
* @param event ACL deleted event
*/
@Subscribe
@AllowConcurrentEvents
public void on(final AclDeletedAsyncEvent event) {
if (log.isInfoEnabled()) {
log.info("ACL deleted event: " + event.toString());
}
TransactionUtil.handle(() -> AppContext.getInstance().getIndexingHandler()
.deleteAcl(event.getSourceId(), event.getPerm(), event.getTargetId()));
}
}

View File

@ -1,14 +1,14 @@
package com.sismics.docs.core.listener.async;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.jpa.ContributorDao;
import com.sismics.docs.core.dao.lucene.LuceneDao;
import com.sismics.docs.core.dao.ContributorDao;
import com.sismics.docs.core.event.DocumentCreatedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.docs.core.util.TransactionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Listener on document created.
@ -25,28 +25,24 @@ public class DocumentCreatedAsyncListener {
* Document created.
*
* @param event Document created event
* @throws Exception
*/
@Subscribe
public void on(final DocumentCreatedAsyncEvent event) throws Exception {
@AllowConcurrentEvents
public void on(final DocumentCreatedAsyncEvent event) {
if (log.isInfoEnabled()) {
log.info("Document created event: " + event.toString());
}
TransactionUtil.handle(new Runnable() {
@Override
public void run() {
// Add the first contributor (the creator of the document)
ContributorDao contributorDao = new ContributorDao();
Contributor contributor = new Contributor();
contributor.setDocumentId(event.getDocument().getId());
contributor.setUserId(event.getUserId());
contributorDao.create(contributor);
}
TransactionUtil.handle(() -> {
// Add the first contributor (the creator of the document)
ContributorDao contributorDao = new ContributorDao();
Contributor contributor = new Contributor();
contributor.setDocumentId(event.getDocument().getId());
contributor.setUserId(event.getUserId());
contributorDao.create(contributor);
// Update index
AppContext.getInstance().getIndexingHandler().createDocument(event.getDocument());
});
// Update Lucene index
LuceneDao luceneDao = new LuceneDao();
luceneDao.createDocument(event.getDocument());
}
}

View File

@ -1,12 +1,13 @@
package com.sismics.docs.core.listener.async;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.util.TransactionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.lucene.LuceneDao;
import com.sismics.docs.core.event.DocumentDeletedAsyncEvent;
/**
* Listener on document deleted.
*
@ -21,17 +22,18 @@ public class DocumentDeletedAsyncListener {
/**
* Document deleted.
*
* @param documentDeletedAsyncEvent Document deleted event
* @throws Exception
* @param event Document deleted event
*/
@Subscribe
public void on(final DocumentDeletedAsyncEvent documentDeletedAsyncEvent) throws Exception {
@AllowConcurrentEvents
public void on(final DocumentDeletedAsyncEvent event) {
if (log.isInfoEnabled()) {
log.info("Document deleted event: " + documentDeletedAsyncEvent.toString());
log.info("Document deleted event: " + event.toString());
}
// Update Lucene index
LuceneDao luceneDao = new LuceneDao();
luceneDao.deleteDocument(documentDeletedAsyncEvent.getDocumentId());
TransactionUtil.handle(() -> {
// Update index
AppContext.getInstance().getIndexingHandler().deleteDocument(event.getDocumentId());
});
}
}

View File

@ -1,17 +1,18 @@
package com.sismics.docs.core.listener.async;
import java.util.List;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.ContributorDao;
import com.sismics.docs.core.dao.DocumentDao;
import com.sismics.docs.core.event.DocumentUpdatedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.Contributor;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.util.TransactionUtil;
import org.slf4j.Logger;
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;
import com.sismics.docs.core.util.TransactionUtil;
import java.util.List;
/**
* Listener on document updated.
@ -28,40 +29,41 @@ public class DocumentUpdatedAsyncListener {
* Document updated.
*
* @param event Document updated event
* @throws Exception
*/
@Subscribe
public void on(final DocumentUpdatedAsyncEvent event) throws Exception {
@AllowConcurrentEvents
public void on(final DocumentUpdatedAsyncEvent event) {
if (log.isInfoEnabled()) {
log.info("Document updated event: " + event.toString());
}
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.getDocumentId());
// Check if the user firing this event is not already a contributor
for (Contributor contributor : contributorList) {
if (contributor.getUserId().equals(event.getUserId())) {
// The current user is already a contributor on this document, don't do anything
return;
}
}
// Add a new contributor
Contributor contributor = new Contributor();
contributor.setDocumentId(event.getDocumentId());
contributor.setUserId(event.getUserId());
contributorDao.create(contributor);
TransactionUtil.handle(() -> {
// Update index
DocumentDao documentDao = new DocumentDao();
Document document = documentDao.getById(event.getDocumentId());
if (document == null) {
// Document deleted since event fired
return;
}
AppContext.getInstance().getIndexingHandler().updateDocument(document);
// Update contributors list
ContributorDao contributorDao = new ContributorDao();
List<Contributor> contributorList = contributorDao.findByDocumentId(event.getDocumentId());
// Check if the user firing this event is not already a contributor
for (Contributor contributor : contributorList) {
if (contributor.getUserId().equals(event.getUserId())) {
// The current user is already a contributor on this document, don't do anything
return;
}
}
// Add a new contributor
Contributor contributor = new Contributor();
contributor.setDocumentId(event.getDocumentId());
contributor.setUserId(event.getUserId());
contributorDao.create(contributor);
});
}
}

View File

@ -1,71 +0,0 @@
package com.sismics.docs.core.listener.async;
import java.text.MessageFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.jpa.FileDao;
import com.sismics.docs.core.dao.lucene.LuceneDao;
import com.sismics.docs.core.event.FileCreatedAsyncEvent;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.util.FileUtil;
import com.sismics.docs.core.util.TransactionUtil;
/**
* Listener on file created.
*
* @author bgamard
*/
public class FileCreatedAsyncListener {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(FileCreatedAsyncListener.class);
/**
* File created.
*
* @param fileCreatedAsyncEvent File created event
* @throws Exception
*/
@Subscribe
public void on(final FileCreatedAsyncEvent fileCreatedAsyncEvent) throws Exception {
if (log.isInfoEnabled()) {
log.info("File created event: " + fileCreatedAsyncEvent.toString());
}
// Guess the mime type a second time, for open document format (first detected as simple ZIP file)
final File file = fileCreatedAsyncEvent.getFile();
// Extract text content from the file
long startTime = System.currentTimeMillis();
final String content = FileUtil.extractContent(fileCreatedAsyncEvent.getLanguage(), file,
fileCreatedAsyncEvent.getInputStream(), fileCreatedAsyncEvent.getPdfInputStream());
fileCreatedAsyncEvent.getInputStream().close();
if (fileCreatedAsyncEvent.getPdfInputStream() != null) {
fileCreatedAsyncEvent.getPdfInputStream().close();
}
log.info(MessageFormat.format("File content extracted in {0}ms", System.currentTimeMillis() - startTime));
// Store the text content in the database
TransactionUtil.handle(new Runnable() {
@Override
public void run() {
FileDao fileDao = new FileDao();
if (fileDao.getActiveById(file.getId()) == null) {
// The file has been deleted since the text extraction started, ignore the result
return;
}
file.setContent(content);
fileDao.update(file);
}
});
// Update Lucene index
LuceneDao luceneDao = new LuceneDao();
luceneDao.createFile(fileCreatedAsyncEvent.getFile());
}
}

View File

@ -1,13 +1,14 @@
package com.sismics.docs.core.listener.async;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.lucene.LuceneDao;
import com.sismics.docs.core.event.FileDeletedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.util.FileUtil;
import com.sismics.docs.core.util.TransactionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Listener on file deleted.
@ -23,21 +24,23 @@ public class FileDeletedAsyncListener {
/**
* File deleted.
*
* @param fileDeletedAsyncEvent File deleted event
* @throws Exception
* @param event File deleted event
* @throws Exception e
*/
@Subscribe
public void on(final FileDeletedAsyncEvent fileDeletedAsyncEvent) throws Exception {
@AllowConcurrentEvents
public void on(final FileDeletedAsyncEvent event) throws Exception {
if (log.isInfoEnabled()) {
log.info("File deleted event: " + fileDeletedAsyncEvent.toString());
log.info("File deleted event: " + event.toString());
}
// Delete the file from storage
File file = fileDeletedAsyncEvent.getFile();
File file = event.getFile();
FileUtil.delete(file);
// Update Lucene index
LuceneDao luceneDao = new LuceneDao();
luceneDao.deleteDocument(file.getId());
TransactionUtil.handle(() -> {
// Update index
AppContext.getInstance().getIndexingHandler().deleteDocument(file.getId());
});
}
}

View File

@ -0,0 +1,159 @@
package com.sismics.docs.core.listener.async;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.FileDao;
import com.sismics.docs.core.dao.UserDao;
import com.sismics.docs.core.event.FileCreatedAsyncEvent;
import com.sismics.docs.core.event.FileEvent;
import com.sismics.docs.core.event.FileUpdatedAsyncEvent;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.DirectoryUtil;
import com.sismics.docs.core.util.EncryptionUtil;
import com.sismics.docs.core.util.FileUtil;
import com.sismics.docs.core.util.TransactionUtil;
import com.sismics.docs.core.util.format.FormatHandler;
import com.sismics.docs.core.util.format.FormatHandlerUtil;
import com.sismics.util.ImageUtil;
import com.sismics.util.Scalr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
/**
* Listener on file processing.
*
* @author bgamard
*/
public class FileProcessingAsyncListener {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(FileProcessingAsyncListener.class);
/**
* File created.
*
* @param event File created event
*/
@Subscribe
@AllowConcurrentEvents
public void on(final FileCreatedAsyncEvent event) {
if (log.isInfoEnabled()) {
log.info("File created event: " + event.toString());
}
TransactionUtil.handle(() -> {
// Generate thumbnail, extract content
processFile(event);
// Update index
AppContext.getInstance().getIndexingHandler().createFile(event.getFile());
});
FileUtil.endProcessingFile(event.getFile().getId());
}
/**
* File updated.
*
* @param event File updated event
*/
@Subscribe
@AllowConcurrentEvents
public void on(final FileUpdatedAsyncEvent event) {
if (log.isInfoEnabled()) {
log.info("File updated event: " + event.toString());
}
TransactionUtil.handle(() -> {
// Generate thumbnail, extract content
processFile(event);
// Update index
AppContext.getInstance().getIndexingHandler().updateFile(event.getFile());
});
FileUtil.endProcessingFile(event.getFile().getId());
}
/**
* Process the file (create/update).
*
* @param event File event
*/
private void processFile(FileEvent event) {
// Find a format handler
final File file = event.getFile();
FormatHandler formatHandler = FormatHandlerUtil.find(file.getMimeType());
if (formatHandler == null) {
log.error("Format unhandled: " + file.getMimeType());
FileUtil.endProcessingFile(file.getId());
return;
}
// Get the user from the database
UserDao userDao = new UserDao();
User user = userDao.getById(event.getUserId());
if (user == null) {
// The user has been deleted meanwhile
FileUtil.endProcessingFile(file.getId());
return;
}
// Generate file variations
try {
Cipher cipher = EncryptionUtil.getEncryptionCipher(user.getPrivateKey());
BufferedImage image = formatHandler.generateThumbnail(event.getUnencryptedFile());
if (image != null) {
// Generate thumbnails from image
BufferedImage web = Scalr.resize(image, Scalr.Method.ULTRA_QUALITY, Scalr.Mode.AUTOMATIC, 1280);
BufferedImage thumbnail = Scalr.resize(image, Scalr.Method.ULTRA_QUALITY, Scalr.Mode.AUTOMATIC, 256);
image.flush();
// Write "web" encrypted image
Path outputFile = DirectoryUtil.getStorageDirectory().resolve(file.getId() + "_web");
try (OutputStream outputStream = new CipherOutputStream(Files.newOutputStream(outputFile), cipher)) {
ImageUtil.writeJpeg(web, outputStream);
}
// Write "thumb" encrypted image
outputFile = DirectoryUtil.getStorageDirectory().resolve(file.getId() + "_thumb");
try (OutputStream outputStream = new CipherOutputStream(Files.newOutputStream(outputFile), cipher)) {
ImageUtil.writeJpeg(thumbnail, outputStream);
}
}
} catch (Exception e) {
log.error("Unable to generate thumbnails", e);
}
// Extract text content from the file
long startTime = System.currentTimeMillis();
String content = null;
try {
content = formatHandler.extractContent(event.getLanguage(), event.getUnencryptedFile());
} catch (Exception e) {
log.error("Error extracting content from: " + event.getFile(), e);
}
log.info(MessageFormat.format("File content extracted in {0}ms", System.currentTimeMillis() - startTime));
// Save the file to database
FileDao fileDao = new FileDao();
if (fileDao.getActiveById(file.getId()) == null) {
// The file has been deleted since the text extraction started, ignore the result
return;
}
file.setContent(content);
fileDao.update(file);
}
}

View File

@ -0,0 +1,52 @@
package com.sismics.docs.core.listener.async;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.dto.UserDto;
import com.sismics.docs.core.event.PasswordLostEvent;
import com.sismics.docs.core.model.jpa.PasswordRecovery;
import com.sismics.docs.core.util.TransactionUtil;
import com.sismics.util.EmailUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* Listener for password recovery requests.
*
* @author jtremeaux
*/
public class PasswordLostAsyncListener {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(PasswordLostAsyncListener.class);
/**
* Handle events.
*
* @param event Event
*/
@Subscribe
@AllowConcurrentEvents
public void on(final PasswordLostEvent event) {
if (log.isInfoEnabled()) {
log.info("Password lost event: " + event.toString());
}
TransactionUtil.handle(() -> {
final UserDto user = event.getUser();
final PasswordRecovery passwordRecovery = event.getPasswordRecovery();
// Send the password recovery email
Map<String, Object> paramRootMap = new HashMap<>();
paramRootMap.put("user_name", user.getUsername());
paramRootMap.put("password_recovery_key", passwordRecovery.getId());
EmailUtil.sendEmail(Constants.EMAIL_TEMPLATE_PASSWORD_RECOVERY, user, paramRootMap);
});
}
}

View File

@ -1,18 +1,18 @@
package com.sismics.docs.core.listener.async;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.FileDao;
import com.sismics.docs.core.dao.lucene.LuceneDao;
import com.sismics.docs.core.dao.DocumentDao;
import com.sismics.docs.core.dao.FileDao;
import com.sismics.docs.core.event.RebuildIndexAsyncEvent;
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.util.TransactionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* Listener on rebuild index.
@ -28,31 +28,27 @@ public class RebuildIndexAsyncListener {
/**
* Rebuild Lucene index.
*
* @param rebuildIndexAsyncEvent Index rebuild event
* @throws Exception
* @param event Index rebuild event
*/
@Subscribe
public void on(final RebuildIndexAsyncEvent rebuildIndexAsyncEvent) throws Exception {
@AllowConcurrentEvents
public void on(final RebuildIndexAsyncEvent event) {
if (log.isInfoEnabled()) {
log.info("Rebuild index event: " + rebuildIndexAsyncEvent.toString());
log.info("Rebuild index event: " + event.toString());
}
// Fetch all documents and files
TransactionUtil.handle(new Runnable() {
@Override
public void run() {
// Fetch all documents
DocumentDao documentDao = new DocumentDao();
List<Document> documentList = documentDao.findAll();
// Fetch all files
FileDao fileDao = new FileDao();
List<File> fileList = fileDao.findAll();
// Rebuild index
LuceneDao luceneDao = new LuceneDao();
luceneDao.rebuildIndex(documentList, fileList);
}
TransactionUtil.handle(() -> {
// Fetch all documents
DocumentDao documentDao = new DocumentDao();
List<Document> documentList = documentDao.findAll();
// Fetch all files
FileDao fileDao = new FileDao();
List<File> fileList = fileDao.findAll();
// Rebuild index
AppContext.getInstance().getIndexingHandler().rebuildIndex(documentList, fileList);
});
}
}

View File

@ -0,0 +1,51 @@
package com.sismics.docs.core.listener.async;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.dto.UserDto;
import com.sismics.docs.core.event.RouteStepValidateEvent;
import com.sismics.docs.core.util.TransactionUtil;
import com.sismics.util.EmailUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
/**
* Listener for route step validate.
*
* @author bgamard
*/
public class RouteStepValidateAsyncListener {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(RouteStepValidateAsyncListener.class);
/**
* Handle events.
*
* @param event Event
*/
@Subscribe
@AllowConcurrentEvents
public void on(final RouteStepValidateEvent event) {
if (log.isInfoEnabled()) {
log.info("Route step validate event: " + event.toString());
}
TransactionUtil.handle(() -> {
final UserDto user = event.getUser();
// Send route step validated email
Map<String, Object> paramRootMap = new HashMap<>();
paramRootMap.put("user_name", user.getUsername());
paramRootMap.put("document_id", event.getDocument().getId());
paramRootMap.put("document_title", event.getDocument().getTitle());
EmailUtil.sendEmail(Constants.EMAIL_TEMPLATE_ROUTE_STEP_VALIDATE, user, paramRootMap);
});
}
}

View File

@ -0,0 +1,103 @@
package com.sismics.docs.core.listener.async;
import com.google.common.collect.Lists;
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.Subscribe;
import com.sismics.docs.core.constant.WebhookEvent;
import com.sismics.docs.core.dao.WebhookDao;
import com.sismics.docs.core.dao.criteria.WebhookCriteria;
import com.sismics.docs.core.dao.dto.WebhookDto;
import com.sismics.docs.core.event.*;
import com.sismics.docs.core.util.TransactionUtil;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
/**
* Listener for triggering webhooks.
*
* @author bgamard
*/
public class WebhookAsyncListener {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(WebhookAsyncListener.class);
/**
* OkHttp client.
*/
private static final OkHttpClient client = new OkHttpClient();
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
@Subscribe
@AllowConcurrentEvents
public void on(final DocumentCreatedAsyncEvent event) {
triggerWebhook(WebhookEvent.DOCUMENT_CREATED, event.getDocument().getId());
}
@Subscribe
@AllowConcurrentEvents
public void on(final DocumentUpdatedAsyncEvent event) {
triggerWebhook(WebhookEvent.DOCUMENT_UPDATED, event.getDocumentId());
}
@Subscribe
@AllowConcurrentEvents
public void on(final DocumentDeletedAsyncEvent event) {
triggerWebhook(WebhookEvent.DOCUMENT_DELETED, event.getDocumentId());
}
@Subscribe
@AllowConcurrentEvents
public void on(final FileCreatedAsyncEvent event) {
triggerWebhook(WebhookEvent.FILE_CREATED, event.getFile().getId());
}
@Subscribe
@AllowConcurrentEvents
public void on(final FileUpdatedAsyncEvent event) {
triggerWebhook(WebhookEvent.FILE_UPDATED, event.getFile().getId());
}
@Subscribe
@AllowConcurrentEvents
public void on(final FileDeletedAsyncEvent event) {
triggerWebhook(WebhookEvent.FILE_DELETED, event.getFile().getId());
}
/**
* Trigger the webhooks for the specified event.
*
* @param event Event
* @param id ID
*/
private void triggerWebhook(WebhookEvent event, String id) {
List<String> webhookUrlList = Lists.newArrayList();
TransactionUtil.handle(() -> {
WebhookDao webhookDao = new WebhookDao();
List<WebhookDto> webhookDtoList = webhookDao.findByCriteria(new WebhookCriteria().setEvent(event), null);
for (WebhookDto webhookDto : webhookDtoList) {
webhookUrlList.add(webhookDto.getUrl());
}
});
RequestBody body = RequestBody.create(JSON, "{\"event\": \"" + event.name() + "\", \"id\": \"" + id + "\"}");
for (String webhookUrl : webhookUrlList) {
Request request = new Request.Builder()
.url(webhookUrl)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
log.info("Successfully called the webhook at: " + webhookUrl + " - " + response.code());
} catch (IOException e) {
log.error("Error calling the webhook at: " + webhookUrl, e);
}
}
}
}

View File

@ -1,28 +0,0 @@
package com.sismics.docs.core.listener.sync;
import com.google.common.eventbus.DeadEvent;
import com.google.common.eventbus.Subscribe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Listener for all unprocessed events.
*
* @author jtremeaux
*/
public class DeadEventListener {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(DeadEventListener.class);
/**
* Process every dead event.
*
* @param deadEvent Catchall event
*/
@Subscribe
public void onDeadEvent(DeadEvent deadEvent) {
log.error("Dead event catched: " + deadEvent.toString());
}
}

View File

@ -1,5 +1,22 @@
package com.sismics.docs.core.model.context;
import com.google.common.collect.Lists;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.UserDao;
import com.sismics.docs.core.event.RebuildIndexAsyncEvent;
import com.sismics.docs.core.listener.async.*;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.service.FileService;
import com.sismics.docs.core.service.InboxService;
import com.sismics.docs.core.util.PdfUtil;
import com.sismics.docs.core.util.indexing.IndexingHandler;
import com.sismics.util.ClasspathScanner;
import com.sismics.util.EnvironmentUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
@ -7,158 +24,221 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.EventBus;
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.dao.jpa.ConfigDao;
import com.sismics.docs.core.listener.async.DocumentCreatedAsyncListener;
import com.sismics.docs.core.listener.async.DocumentDeletedAsyncListener;
import com.sismics.docs.core.listener.async.DocumentUpdatedAsyncListener;
import com.sismics.docs.core.listener.async.FileCreatedAsyncListener;
import com.sismics.docs.core.listener.async.FileDeletedAsyncListener;
import com.sismics.docs.core.listener.async.RebuildIndexAsyncListener;
import com.sismics.docs.core.listener.sync.DeadEventListener;
import com.sismics.docs.core.model.jpa.Config;
import com.sismics.docs.core.service.IndexingService;
import com.sismics.util.EnvironmentUtil;
/**
* Global application context.
*
* @author jtremeaux
* @author jtremeaux
*/
public class AppContext {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(AppContext.class);
/**
* Singleton instance.
*/
private static AppContext instance;
/**
* Event bus.
*/
private EventBus eventBus;
/**
* Generic asynchronous event bus.
*/
private EventBus asyncEventBus;
/**
* Indexing service.
* Asynchronous bus for email sending.
*/
private IndexingService indexingService;
private EventBus mailEventBus;
/**
* Indexing handler.
*/
private IndexingHandler indexingHandler;
/**
* Inbox scanning service.
*/
private InboxService inboxService;
/**
* File service.
*/
private FileService fileService;
/**
* Asynchronous executors.
*/
private List<ExecutorService> asyncExecutorList;
private List<ThreadPoolExecutor> asyncExecutorList;
/**
* Private constructor.
* Start the application context.
*/
private AppContext() {
private void startUp() {
resetEventBus();
ConfigDao configDao = new ConfigDao();
Config luceneStorageConfig = configDao.getById(ConfigType.LUCENE_DIRECTORY_STORAGE);
indexingService = new IndexingService(luceneStorageConfig != null ? luceneStorageConfig.getValue() : null);
indexingService.startAsync();
// Start indexing handler
try {
List<Class<? extends IndexingHandler>> indexingHandlerList = Lists.newArrayList(
new ClasspathScanner<IndexingHandler>().findClasses(IndexingHandler.class, "com.sismics.docs.core.util.indexing"));
for (Class<? extends IndexingHandler> handlerClass : indexingHandlerList) {
IndexingHandler handler = handlerClass.newInstance();
if (handler.accept()) {
indexingHandler = handler;
break;
}
}
indexingHandler.startUp();
} catch (Exception e) {
log.error("Error starting the indexing handler, rebuilding the index: " + e.getMessage());
RebuildIndexAsyncEvent rebuildIndexAsyncEvent = new RebuildIndexAsyncEvent();
asyncEventBus.post(rebuildIndexAsyncEvent);
}
// Start file service
fileService = new FileService();
fileService.startAsync();
fileService.awaitRunning();
// Start inbox service
inboxService = new InboxService();
inboxService.startAsync();
inboxService.awaitRunning();
// Register fonts
PdfUtil.registerFonts();
// Change the admin password if needed
String envAdminPassword = System.getenv(Constants.ADMIN_PASSWORD_INIT_ENV);
if (envAdminPassword != null) {
UserDao userDao = new UserDao();
User adminUser = userDao.getById("admin");
if (Constants.DEFAULT_ADMIN_PASSWORD.equals(adminUser.getPassword())) {
adminUser.setPassword(envAdminPassword);
userDao.updateHashedPassword(adminUser);
}
}
// Change the admin email if needed
String envAdminEmail = System.getenv(Constants.ADMIN_EMAIL_INIT_ENV);
if (envAdminEmail != null) {
UserDao userDao = new UserDao();
User adminUser = userDao.getById("admin");
if (Constants.DEFAULT_ADMIN_EMAIL.equals(adminUser.getEmail())) {
adminUser.setEmail(envAdminEmail);
userDao.update(adminUser, "admin");
}
}
}
/**
* (Re)-initializes the event buses.
*/
private void resetEventBus() {
eventBus = new EventBus();
eventBus.register(new DeadEventListener());
asyncExecutorList = new ArrayList<>();
asyncEventBus = newAsyncEventBus();
asyncEventBus.register(new FileCreatedAsyncListener());
asyncEventBus.register(new FileProcessingAsyncListener());
asyncEventBus.register(new FileDeletedAsyncListener());
asyncEventBus.register(new DocumentCreatedAsyncListener());
asyncEventBus.register(new DocumentUpdatedAsyncListener());
asyncEventBus.register(new DocumentDeletedAsyncListener());
asyncEventBus.register(new RebuildIndexAsyncListener());
asyncEventBus.register(new AclCreatedAsyncListener());
asyncEventBus.register(new AclDeletedAsyncListener());
asyncEventBus.register(new WebhookAsyncListener());
mailEventBus = newAsyncEventBus();
mailEventBus.register(new PasswordLostAsyncListener());
mailEventBus.register(new RouteStepValidateAsyncListener());
}
/**
* Returns a single instance of the application context.
*
*
* @return Application context
*/
public static AppContext getInstance() {
if (instance == null) {
instance = new AppContext();
instance.startUp();
}
return instance;
}
/**
* Wait for termination of all asynchronous events.
* /!\ Must be used only in unit tests and never a multi-user environment.
*/
public void waitForAsync() {
if (EnvironmentUtil.isUnitTest()) {
return;
}
try {
for (ExecutorService executor : asyncExecutorList) {
// Shutdown executor, don't accept any more tasks (can cause error with nested events)
try {
executor.shutdown();
executor.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// NOP
}
}
} finally {
resetEventBus();
}
}
/**
* Creates a new asynchronous event bus.
*
*
* @return Async event bus
*/
private EventBus newAsyncEventBus() {
if (EnvironmentUtil.isUnitTest()) {
return new EventBus();
} else {
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
ThreadPoolExecutor executor = new ThreadPoolExecutor(8, 8,
1L, TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
asyncExecutorList.add(executor);
return new AsyncEventBus(executor);
}
}
/**
* Getter of eventBus.
* Return the current number of queued tasks waiting to be processed.
*
* @return eventBus
* @return Number of queued tasks
*/
public EventBus getEventBus() {
return eventBus;
public int getQueuedTaskCount() {
int queueSize = 0;
for (ThreadPoolExecutor executor : asyncExecutorList) {
queueSize += executor.getQueue().size();
}
return queueSize;
}
/**
* Getter of asyncEventBus.
*
* @return asyncEventBus
*/
public EventBus getAsyncEventBus() {
return asyncEventBus;
}
/**
* Getter of indexingService.
*
* @return indexingService
*/
public IndexingService getIndexingService() {
return indexingService;
public EventBus getMailEventBus() {
return mailEventBus;
}
public IndexingHandler getIndexingHandler() {
return indexingHandler;
}
public InboxService getInboxService() {
return inboxService;
}
public FileService getFileService() {
return fileService;
}
public void shutDown() {
for (ExecutorService executor : asyncExecutorList) {
// Shutdown executor, don't accept any more tasks (can cause error with nested events)
try {
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
// NOP
}
}
if (indexingHandler != null) {
indexingHandler.shutDown();
}
if (inboxService != null) {
inboxService.stopAsync();
inboxService.awaitTerminated();
}
if (fileService != null) {
fileService.stopAsync();
}
instance = null;
}
}

View File

@ -1,17 +1,12 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.MoreObjects;
import com.sismics.docs.core.constant.AclType;
import com.sismics.docs.core.constant.PermType;
import javax.persistence.*;
import java.util.Date;
/**
* ACL entity.
*
@ -34,6 +29,13 @@ public class Acl implements Loggable {
@Enumerated(EnumType.STRING)
private PermType perm;
/**
* ACL type.
*/
@Column(name = "ACL_TYPE_C", length = 30, nullable = false)
@Enumerated(EnumType.STRING)
private AclType type;
/**
* ACL source ID.
*/
@ -83,7 +85,16 @@ public class Acl implements Loggable {
public void setTargetId(String targetId) {
this.targetId = targetId;
}
public AclType getType() {
return type;
}
public Acl setType(AclType type) {
this.type = type;
return this;
}
@Override
public Date getDeleteDate() {
return deleteDate;
@ -100,6 +111,7 @@ public class Acl implements Loggable {
.add("perm", perm)
.add("sourceId", sourceId)
.add("targetId", targetId)
.add("type", type)
.toString();
}

View File

@ -1,13 +1,12 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import com.google.common.base.MoreObjects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.MoreObjects;
import java.util.Date;
/**
* Document entity.
@ -102,6 +101,12 @@ public class Document implements Loggable {
@Column(name = "DOC_CREATEDATE_D", nullable = false)
private Date createDate;
/**
* Creation date.
*/
@Column(name = "DOC_UPDATEDATE_D", nullable = false)
private Date updateDate;
/**
* Deletion date.
*/
@ -229,6 +234,14 @@ public class Document implements Loggable {
this.deleteDate = deleteDate;
}
public Date getUpdateDate() {
return updateDate;
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)

View File

@ -1,18 +1,12 @@
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.Lob;
import javax.persistence.Table;
import javax.persistence.Transient;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.sismics.util.mime.MimeTypeUtil;
import javax.persistence.*;
import java.util.Date;
/**
* File entity.
*
@ -176,7 +170,8 @@ public class File implements Loggable {
@Override
public String toMessage() {
return documentId;
// Attached document ID and name concatenated
return (documentId == null ? Strings.repeat(" ", 36) : documentId) + name;
}
/**

View File

@ -14,12 +14,12 @@ public interface Loggable {
*
* @return Entity message
*/
public String toMessage();
String toMessage();
/**
* Loggable are soft deletable.
*
* @return deleteDate
*/
public Date getDeleteDate();
Date getDeleteDate();
}

View File

@ -0,0 +1,82 @@
package com.sismics.docs.core.model.jpa;
import com.google.common.base.MoreObjects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
/**
* Password recovery entity.
*
* @author jtremeaux
*/
@Entity
@Table(name = "T_PASSWORD_RECOVERY")
public class PasswordRecovery {
/**
* Identifier.
*/
@Id
@Column(name = "PWR_ID_C", length = 36)
private String id;
/**
* Username.
*/
@Column(name = "PWR_USERNAME_C", nullable = false, length = 50)
private String username;
/**
* Creation date.
*/
@Column(name = "PWR_CREATEDATE_D", nullable = false)
private Date createDate;
/**
* Delete date.
*/
@Column(name = "PWR_DELETEDATE_D")
private Date deleteDate;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getDeleteDate() {
return deleteDate;
}
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.toString();
}
}

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