Initial commit

This commit is contained in:
jendib 2013-07-27 18:33:20 +02:00
parent 41cb6dd9ae
commit 9b74bd8194
156 changed files with 72879 additions and 0 deletions

15
.gitattributes vendored Normal file
View File

@ -0,0 +1,15 @@
# Set default behaviour, in case users don't have core.autocrlf set.
* text=auto
# Explicitly declare text files we want to always be normalized and converted
# to native line endings on checkout.
*.c text
*.h text
# Declare files that will always have CRLF line endings on checkout.
*.sln text eol=crlf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
/*/.classpath
/*/.project
/*/.settings
/*/bin
/*/gen
/*/target
/*/*.iml

185
docs-core/pom.xml Normal file
View File

@ -0,0 +1,185 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../docs-parent</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>docs-core</artifactId>
<packaging>jar</packaging>
<name>Docs Core</name>
<dependencies>
<!-- Persistence layer dependencies -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<!-- Other external dependencies -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
</dependency>
<dependency>
<groupId>org.ccil.cowan.tagsoup</groupId>
<artifactId>tagsoup</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
</dependency>
<dependency>
<groupId>org.bitlet</groupId>
<artifactId>weupnp</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<!-- Development profile (active by default) -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>env</name>
<value>dev</value>
</property>
</activation>
<build>
<resources>
<resource>
<directory>src/dev/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</profile>
<!-- Production profile -->
<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>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>

View File

@ -0,0 +1,13 @@
package com.sismics.docs.core.constant;
/**
* Configuration parameters.
*
* @author jtremeaux
*/
public enum ConfigType {
/**
* Lucene directory storage type.
*/
LUCENE_DIRECTORY_STORAGE,
}

View File

@ -0,0 +1,43 @@
package com.sismics.docs.core.constant;
/**
* Application constants.
*
* @author jtremeaux
*/
public class Constants {
/**
* Default locale.
*/
public static final String DEFAULT_LOCALE_ID = "en";
/**
* Default timezone ID.
*/
public static final String DEFAULT_TIMEZONE_ID = "Europe/London";
/**
* Default theme ID.
*/
public static final String DEFAULT_THEME_ID = "default.less";
/**
* Administrator's default password ("admin").
*/
public static final String DEFAULT_ADMIN_PASSWORD = "$2a$05$6Ny3TjrW3aVAL1or2SlcR.fhuDgPKp5jp.P9fBXwVNePgeLqb4i3C";
/**
* RAM Lucene directory storage.
*/
public static final String LUCENE_DIRECTORY_STORAGE_RAM = "RAM";
/**
* File Lucene directory storage.
*/
public static final String LUCENE_DIRECTORY_STORAGE_FILE = "FILE";
/**
* Default generic user role.
*/
public static final String DEFAULT_USER_ROLE = "admin";
}

View File

@ -0,0 +1,38 @@
package com.sismics.docs.core.dao.file.theme;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.Lists;
import com.sismics.docs.core.util.DirectoryUtil;
/**
* Theme DAO.
*
* @author jtremeaux
*/
public class ThemeDao {
private final static FilenameFilter CSS_FILTER = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(".css") || name.endsWith(".less");
}
};
/**
* Return the list of all themes.
*
* @return List of themes
*/
public List<String> findAll() {
final File themeDirectory = DirectoryUtil.getThemeDirectory();
if (themeDirectory != null) {
return Lists.newArrayList(themeDirectory.list(CSS_FILTER));
} else {
return new ArrayList<String>();
}
}
}

View File

@ -0,0 +1,99 @@
package com.sismics.docs.core.dao.jpa;
import java.util.Date;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.joda.time.DateTime;
import com.sismics.docs.core.model.jpa.AuthenticationToken;
import com.sismics.util.context.ThreadLocalContext;
/**
* Authentication token DAO.
*
* @author jtremeaux
*/
public class AuthenticationTokenDao {
/**
* Gets an authentication token.
*
* @param id Authentication token ID
* @return Authentication token
*/
public AuthenticationToken get(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
return em.find(AuthenticationToken.class, id);
}
/**
* Creates a new authentication token.
*
* @param authenticationToken Authentication token
* @return Authentication token ID
*/
public String create(AuthenticationToken authenticationToken) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
authenticationToken.setId(UUID.randomUUID().toString());
authenticationToken.setCreationDate(new Date());
em.persist(authenticationToken);
return authenticationToken.getId();
}
/**
* Deletes the authentication token.
*
* @param authenticationTokenId Authentication token ID
* @throws Exception
*/
public void delete(String authenticationTokenId) throws Exception {
EntityManager em = ThreadLocalContext.get().getEntityManager();
AuthenticationToken authenticationToken = em.find(AuthenticationToken.class, authenticationTokenId);
if (authenticationToken != null) {
em.remove(authenticationToken);
} else {
throw new Exception("Token not found: " + authenticationTokenId);
}
}
/**
* Deletes old short lived tokens.
*
* @param userId User ID
* @throws Exception
*/
public void deleteOldSessionToken(String userId) {
StringBuilder sb = new StringBuilder("delete from T_AUTHENTICATION_TOKEN AS ato ");
sb.append(" where ato.AUT_IDUSER_C = :userId and ato.AUT_LONGLASTED_B = :longLasted");
sb.append(" and ato.AUT_LASTCONNECTIONDATE_D < :minDate ");
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createNativeQuery(sb.toString());
q.setParameter("userId", userId);
q.setParameter("longLasted", false);
q.setParameter("minDate", DateTime.now().minusDays(1).toDate());
q.executeUpdate();
}
/**
* Deletes old short lived tokens.
*
* @param id Token id
* @throws Exception
*/
public void updateLastConnectionDate(String id) {
StringBuilder sb = new StringBuilder("update T_AUTHENTICATION_TOKEN ato ");
sb.append(" set ato.AUT_LASTCONNECTIONDATE_D = :currentDate ");
sb.append(" where ato.AUT_ID_C = :id");
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createNativeQuery(sb.toString());
q.setParameter("currentDate", new Date());
q.setParameter("id", id);
q.executeUpdate();
}
}

View File

@ -0,0 +1,36 @@
package com.sismics.docs.core.dao.jpa;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.model.jpa.Config;
import com.sismics.util.context.ThreadLocalContext;
/**
* Configuration parameter DAO.
*
* @author jtremeaux
*/
public class ConfigDao {
/**
* Gets a configuration parameter by its ID.
*
* @param id Configuration parameter ID
* @return Configuration parameter
*/
public Config getById(ConfigType id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Prevents from getting parameters outside of a transactional context (e.g. jUnit)
if (em == null) {
return null;
}
try {
return em.find(Config.class, id);
} catch (NoResultException e) {
return null;
}
}
}

View File

@ -0,0 +1,147 @@
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 com.google.common.base.Joiner;
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.model.jpa.Document;
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;
/**
* Document DAO.
*
* @author bgamard
*/
public class DocumentDao {
/**
* Creates a new document.
*
* @param document Document
* @return New ID
* @throws Exception
*/
public String create(Document document) {
// Create the UUID
document.setId(UUID.randomUUID().toString());
// Create the document
EntityManager em = ThreadLocalContext.get().getEntityManager();
document.setCreateDate(new Date());
em.persist(document);
return document.getId();
}
/**
* Returns an active document.
*
* @param id Document ID
* @param userId User ID
* @return Document
*/
public Document getDocument(String id, String userId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select d from Document d where d.id = :id and d.userId = :userId and d.deleteDate is null");
q.setParameter("id", id);
q.setParameter("userId", userId);
return (Document) q.getSingleResult();
}
/**
* Deletes a document.
*
* @param id Document ID
*/
public void delete(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the document
Query q = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null");
q.setParameter("id", id);
Document documentDb = (Document) q.getSingleResult();
// Delete the document
Date dateNow = new Date();
documentDb.setDeleteDate(dateNow);
// Delete linked data
q = em.createQuery("update File f set f.deleteDate = :dateNow where f.documentId = :documentId and f.deleteDate is null");
q.setParameter("documentId", documentDb.getId());
q.setParameter("dateNow", dateNow);
q.executeUpdate();
}
/**
* Gets a document by its ID.
*
* @param id Document ID
* @return Document
*/
public Document getById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
return em.find(Document.class, id);
} catch (NoResultException e) {
return null;
}
}
/**
* Searches documents by criteria.
*
* @param paginatedList List of documents (updated by side effects)
* @param criteria Search criteria
* @return List of document
*/
public void findByCriteria(PaginatedList<DocumentDto> paginatedList, DocumentCriteria criteria, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
StringBuilder sb = new StringBuilder("select d.DOC_ID_C c0, d.DOC_TITLE_C c1, d.DOC_DESCRIPTION_C c2, d.DOC_CREATEDATE_D c3");
sb.append(" from T_DOCUMENT d ");
// Adds search criteria
List<String> criteriaList = new ArrayList<String>();
if (criteria.getUserId() != null) {
criteriaList.add("d.DOC_IDUSER_C = :userId");
parameterMap.put("userId", criteria.getUserId());
}
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<DocumentDto>();
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());
documentDtoList.add(documentDto);
}
paginatedList.setResultList(documentDtoList);
}
}

View File

@ -0,0 +1,102 @@
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;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.util.context.ThreadLocalContext;
/**
* File DAO.
*
* @author bgamard
*/
public class FileDao {
/**
* Creates a new file.
*
* @param file File
* @return New ID
* @throws Exception
*/
public String create(File file) {
// Create the UUID
file.setId(UUID.randomUUID().toString());
// Create the file
EntityManager em = ThreadLocalContext.get().getEntityManager();
file.setCreateDate(new Date());
em.persist(file);
return file.getId();
}
/**
* Returns an active file.
*
* @param id File ID
* @return Document
*/
public File getFile(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null");
q.setParameter("id", id);
return (File) q.getSingleResult();
}
/**
* Deletes a file.
*
* @param id File ID
*/
public void delete(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the document
Query q = em.createQuery("select f from File f where f.id = :id and f.deleteDate is null");
q.setParameter("id", id);
File fileDb = (File) q.getSingleResult();
// Delete the document
Date dateNow = new Date();
fileDb.setDeleteDate(dateNow);
}
/**
* Gets a file by its ID.
*
* @param id Document ID
* @return Document
*/
public File getById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
return em.find(File.class, id);
} catch (NoResultException e) {
return null;
}
}
/**
* Get files by document ID.
*
* @param documentId Document ID
* @return List of files
*/
@SuppressWarnings("unchecked")
public List<File> getByDocumentId(String documentId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// Get the files
Query q = em.createQuery("select f from File f where f.documentId = :documentId and f.deleteDate is null");
q.setParameter("documentId", documentId);
List<File> files = (List<File>) q.getResultList();
return files;
}
}

View File

@ -0,0 +1,44 @@
package com.sismics.docs.core.dao.jpa;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.sismics.docs.core.model.jpa.Locale;
import com.sismics.util.context.ThreadLocalContext;
/**
* Locale DAO.
*
* @author jtremeaux
*/
public class LocaleDao {
/**
* Gets a locale by its ID.
*
* @param id Locale ID
* @return Locale
*/
public Locale getById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
return em.find(Locale.class, id);
} catch (NoResultException e) {
return null;
}
}
/**
* Returns the list of all locales.
*
* @return List of locales
*/
@SuppressWarnings("unchecked")
public List<Locale> findAll() {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select l from Locale l order by l.id");
return q.getResultList();
}
}

View File

@ -0,0 +1,33 @@
package com.sismics.docs.core.dao.jpa;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.google.common.collect.Sets;
import com.sismics.util.context.ThreadLocalContext;
/**
* Role base functions DAO.
*
* @author jtremeaux
*/
public class RoleBaseFunctionDao {
/**
* Find the set of base functions of a role.
*
* @param roleId Role ID
* @return Set of base functions
*/
@SuppressWarnings("unchecked")
public Set<String> findByRoleId(String roleId) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
StringBuilder sb = new StringBuilder("select rbf.RBF_IDBASEFUNCTION_C from T_ROLE_BASE_FUNCTION rbf, T_ROLE r");
sb.append(" where rbf.RBF_IDROLE_C = :roleId and rbf.RBF_DELETEDATE_D is null");
sb.append(" and r.ROL_ID_C = rbf.RBF_IDROLE_C and r.ROL_DELETEDATE_D is null");
Query q = em.createNativeQuery(sb.toString());
q.setParameter("roleId", roleId);
return Sets.newHashSet(q.getResultList());
}
}

View File

@ -0,0 +1,246 @@
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;
import com.google.common.base.Joiner;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.jpa.dto.UserDto;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.QueryParam;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.util.context.ThreadLocalContext;
/**
* User DAO.
*
* @author jtremeaux
*/
public class UserDao {
/**
* Authenticates an user.
*
* @param username User login
* @param password User password
* @return ID of the authenticated user or null
*/
public String authenticate(String username, String password) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select u from User u where u.username = :username and u.deleteDate is null");
q.setParameter("username", username);
try {
User user = (User) q.getSingleResult();
if (!BCrypt.checkpw(password, user.getPassword())) {
return null;
}
return user.getId();
} catch (NoResultException e) {
return null;
}
}
/**
* Creates a new user.
*
* @param user User to create
* @return User ID
* @throws Exception
*/
public String create(User user) throws Exception {
// Create the user UUID
user.setId(UUID.randomUUID().toString());
// Checks for user unicity
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query q = em.createQuery("select u from User u where u.username = :username and u.deleteDate is null");
q.setParameter("username", user.getUsername());
List<?> l = q.getResultList();
if (l.size() > 0) {
throw new Exception("AlreadyExistingUsername");
}
user.setCreateDate(new Date());
user.setPassword(hashPassword(user.getPassword()));
user.setTheme(Constants.DEFAULT_THEME_ID);
em.persist(user);
return user.getId();
}
/**
* Updates a user.
*
* @param user User to update
* @return Updated user
*/
public User update(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();
// Update the user
userFromDb.setLocaleId(user.getLocaleId());
userFromDb.setEmail(user.getEmail());
userFromDb.setTheme(user.getTheme());
userFromDb.setFirstConnection(user.isFirstConnection());
return user;
}
/**
* Update the user password.
*
* @param user User to update
* @return Updated user
*/
public User updatePassword(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();
// Update the user
userFromDb.setPassword(hashPassword(user.getPassword()));
return user;
}
/**
* Gets a user by its ID.
*
* @param id User ID
* @return User
*/
public User getById(String id) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
return em.find(User.class, id);
} catch (NoResultException e) {
return null;
}
}
/**
* Gets an active user by its username.
*
* @param username User's username
* @return User
*/
public User getActiveByUsername(String username) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
Query q = em.createQuery("select u from User u where u.username = :username and u.deleteDate is null");
q.setParameter("username", username);
return (User) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Gets an active user by its password recovery token.
*
* @param passwordResetKey Password recovery token
* @return User
*/
public User getActiveByPasswordResetKey(String passwordResetKey) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
try {
Query q = em.createQuery("select u from User u where u.passwordResetKey = :passwordResetKey and u.deleteDate is null");
q.setParameter("passwordResetKey", passwordResetKey);
return (User) q.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
/**
* Deletes a user.
*
* @param username User's username
*/
public void delete(String username) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
// 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();
// Delete the user
Date dateNow = new Date();
userFromDb.setDeleteDate(dateNow);
// Delete linked data
q = em.createQuery("delete from AuthenticationToken at where at.userId = :userId");
q.setParameter("userId", userFromDb.getId());
q.executeUpdate();
}
/**
* Hash the user's password.
*
* @param password Clear password
* @return Hashed password
*/
protected String hashPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt());
}
/**
* Returns the list of all users.
*
* @param paginatedList List of users (updated by side effects)
* @param sortCriteria Sort criteria
*/
public void findAll(PaginatedList<UserDto> paginatedList, SortCriteria sortCriteria) {
Map<String, Object> parameterMap = new HashMap<String, Object>();
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_IDLOCALE_C as c4");
sb.append(" from T_USER u ");
// Add search criterias
List<String> criteriaList = new ArrayList<String>();
criteriaList.add("u.USE_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<UserDto> userDtoList = new ArrayList<UserDto>();
for (Object[] o : l) {
int i = 0;
UserDto userDto = new UserDto();
userDto.setId((String) o[i++]);
userDto.setUsername((String) o[i++]);
userDto.setEmail((String) o[i++]);
userDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
userDto.setLocaleId((String) o[i++]);
userDtoList.add(userDto);
}
paginatedList.setResultList(userDtoList);
}
}

View File

@ -0,0 +1,32 @@
package com.sismics.docs.core.dao.jpa.criteria;
/**
* Document criteria.
*
* @author bgamard
*/
public class DocumentCriteria {
/**
* User ID.
*/
private String userId;
/**
* Getter of userId.
*
* @return userId
*/
public String getUserId() {
return userId;
}
/**
* Setter of userId.
*
* @param userId userId
*/
public void setUserId(String userId) {
this.userId = userId;
}
}

View File

@ -0,0 +1,103 @@
package com.sismics.docs.core.dao.jpa.dto;
import javax.persistence.Id;
/**
* Document DTO.
*
* @author jtremeaux
*/
public class DocumentDto {
/**
* Document ID.
*/
@Id
private String id;
/**
* Title.
*/
private String title;
/**
* Description.
*/
private String description;
/**
* Creation date.
*/
private Long createTimestamp;
/**
* Getter de id.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Setter de id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
/**
* Getter de title.
*
* @return the title
*/
public String getTitle() {
return title;
}
/**
* Setter de title.
*
* @param title title
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Getter de description.
*
* @return the description
*/
public String getDescription() {
return description;
}
/**
* Setter de description.
*
* @param description description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Getter de createTimestamp.
*
* @return the createTimestamp
*/
public Long getCreateTimestamp() {
return createTimestamp;
}
/**
* Setter of createTimestamp.
*
* @param createTimestamp createTimestamp
*/
public void setCreateTimestamp(Long createTimestamp) {
this.createTimestamp = createTimestamp;
}
}

View File

@ -0,0 +1,123 @@
package com.sismics.docs.core.dao.jpa.dto;
/**
* User DTO.
*
* @author jtremeaux
*/
public class UserDto {
/**
* User ID.
*/
private String id;
/**
* Locale ID.
*/
private String localeId;
/**
* Username.
*/
private String username;
/**
* Email address.
*/
private String email;
/**
* Creation date of this user.
*/
private Long createTimestamp;
/**
* Getter of id.
*
* @return id
*/
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
/**
* Getter of localeId.
*
* @return localeId
*/
public String getLocaleId() {
return localeId;
}
/**
* Setter of localeId.
*
* @param localeId localeId
*/
public void setLocaleId(String localeId) {
this.localeId = localeId;
}
/**
* Getter of username.
*
* @return username
*/
public String getUsername() {
return username;
}
/**
* Setter of username.
*
* @param username username
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Getter of email.
*
* @return email
*/
public String getEmail() {
return email;
}
/**
* Setter of email.
*
* @param email email
*/
public void setEmail(String email) {
this.email = email;
}
/**
* Getter of createTimestamp.
*
* @return createTimestamp
*/
public Long getCreateTimestamp() {
return createTimestamp;
}
/**
* Setter of createTimestamp.
*
* @param createTimestamp createTimestamp
*/
public void setCreateTimestamp(Long createTimestamp) {
this.createTimestamp = createTimestamp;
}
}

View File

@ -0,0 +1,11 @@
package com.sismics.docs.core.dao.lucene;
/**
* Lucene Article DAO.
*
* @author bgamard
*/
public class ArticleDao {
}

View File

@ -0,0 +1,135 @@
package com.sismics.docs.core.dao.lucene;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.IOException;
import java.io.Reader;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.charfilter.HTMLStripCharFilter;
import org.apache.lucene.analysis.core.LowerCaseFilter;
import org.apache.lucene.analysis.core.StopAnalyzer;
import org.apache.lucene.analysis.core.StopFilter;
import org.apache.lucene.analysis.standard.ClassicAnalyzer;
import org.apache.lucene.analysis.standard.ClassicTokenizer;
import org.apache.lucene.analysis.standard.StandardFilter;
import org.apache.lucene.analysis.standard.StandardTokenizer;
import org.apache.lucene.analysis.util.CharArraySet;
import org.apache.lucene.analysis.util.StopwordAnalyzerBase;
import org.apache.lucene.util.Version;
/**
* Filters {@link StandardTokenizer} with {@link StandardFilter}, {@link
* LowerCaseFilter} and {@link StopFilter}, using a list of
* English stop words.
*
* <a name="version"/>
* <p>You must specify the required {@link Version}
* compatibility when creating StandardAnalyzer:
* <ul>
* <li> As of 3.4, Hiragana and Han characters are no longer wrongly split
* from their combining characters. If you use a previous version number,
* you get the exact broken behavior for backwards compatibility.
* <li> As of 3.1, StandardTokenizer implements Unicode text segmentation,
* and StopFilter correctly handles Unicode 4.0 supplementary characters
* in stopwords. {@link ClassicTokenizer} and {@link ClassicAnalyzer}
* are the pre-3.1 implementations of StandardTokenizer and
* StandardAnalyzer.
* <li> As of 2.9, StopFilter preserves position increments
* <li> As of 2.4, Tokens incorrectly identified as acronyms
* are corrected (see <a href="https://issues.apache.org/jira/browse/LUCENE-1068">LUCENE-1068</a>)
* </ul>
*/
public final class DocsStandardAnalyzer extends StopwordAnalyzerBase {
/** Default maximum allowed token length */
public static final int DEFAULT_MAX_TOKEN_LENGTH = 255;
private int maxTokenLength = DEFAULT_MAX_TOKEN_LENGTH;
/** An unmodifiable set containing some common English words that are usually not
useful for searching. */
public static final CharArraySet STOP_WORDS_SET = StopAnalyzer.ENGLISH_STOP_WORDS_SET;
/** Builds an analyzer with the given stop words.
* @param matchVersion Lucene version to match See {@link
* <a href="#version">above</a>}
* @param stopWords stop words */
public DocsStandardAnalyzer(Version matchVersion, CharArraySet stopWords) {
super(matchVersion, stopWords);
}
/** Builds an analyzer with the default stop words ({@link
* #STOP_WORDS_SET}).
* @param matchVersion Lucene version to match See {@link
* <a href="#version">above</a>}
*/
public DocsStandardAnalyzer(Version matchVersion) {
this(matchVersion, STOP_WORDS_SET);
}
/** Builds an analyzer with the stop words from the given reader.
* @see WordlistLoader#getWordSet(Reader, Version)
* @param matchVersion Lucene version to match See {@link
* <a href="#version">above</a>}
* @param stopwords Reader to read stop words from */
public DocsStandardAnalyzer(Version matchVersion, Reader stopwords) throws IOException {
this(matchVersion, loadStopwordSet(stopwords, matchVersion));
}
/**
* Set maximum allowed token length. If a token is seen
* that exceeds this length then it is discarded. This
* setting only takes effect the next time tokenStream or
* tokenStream is called.
*/
public void setMaxTokenLength(int length) {
maxTokenLength = length;
}
/**
* @see #setMaxTokenLength
*/
public int getMaxTokenLength() {
return maxTokenLength;
}
@Override
protected TokenStreamComponents createComponents(final String fieldName, final Reader reader) {
final StandardTokenizer src = new StandardTokenizer(matchVersion, reader);
src.setMaxTokenLength(maxTokenLength);
TokenStream tok = new StandardFilter(matchVersion, src);
tok = new LowerCaseFilter(matchVersion, tok);
tok = new StopFilter(matchVersion, tok, stopwords);
return new TokenStreamComponents(src, tok) {
@Override
protected void setReader(final Reader reader) throws IOException {
src.setMaxTokenLength(DocsStandardAnalyzer.this.maxTokenLength);
super.setReader(reader);
}
};
}
@Override
protected Reader initReader(String fieldName, Reader reader) {
if (fieldName.equals("title") || fieldName.equals("description")) {
return new HTMLStripCharFilter(super.initReader(fieldName, reader));
}
return super.initReader(fieldName, reader);
}
}

View File

@ -0,0 +1,29 @@
package com.sismics.docs.core.listener.sync;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.DeadEvent;
import com.google.common.eventbus.Subscribe;
/**
* 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

@ -0,0 +1,170 @@
package com.sismics.docs.core.model.context;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.lucene.store.Directory;
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.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
*/
public class AppContext {
/**
* Singleton instance.
*/
private static AppContext instance;
/**
* Event bus.
*/
private EventBus eventBus;
/**
* Generic asynchronous event bus.
*/
private EventBus asyncEventBus;
/**
* Indexing service.
*/
private IndexingService indexingService;
/**
* Lucene directory.
*/
private Directory luceneDirectory;
/**
* Asynchronous executors.
*/
private List<ExecutorService> asyncExecutorList;
/**
* Private constructor.
*/
private AppContext() {
resetEventBus();
ConfigDao configDao = new ConfigDao();
Config luceneStorageConfig = configDao.getById(ConfigType.LUCENE_DIRECTORY_STORAGE);
indexingService = new IndexingService(luceneStorageConfig != null ? luceneStorageConfig.getValue() : null);
indexingService.startAndWait();
luceneDirectory = indexingService.getDirectory();
}
/**
* (Re)-initializes the event buses.
*/
private void resetEventBus() {
eventBus = new EventBus();
eventBus.register(new DeadEventListener());
asyncExecutorList = new ArrayList<ExecutorService>();
asyncEventBus = newAsyncEventBus();
}
/**
* Returns a single instance of the application context.
*
* @return Application context
*/
public static AppContext getInstance() {
if (instance == null) {
instance = new AppContext();
}
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>());
asyncExecutorList.add(executor);
return new AsyncEventBus(executor);
}
}
/**
* Getter of eventBus.
*
* @return eventBus
*/
public EventBus getEventBus() {
return eventBus;
}
/**
* Getter of asyncEventBus.
*
* @return asyncEventBus
*/
public EventBus getAsyncEventBus() {
return asyncEventBus;
}
/**
* Getter of feedService.
*
* @return feedService
*/
public IndexingService getIndexingService() {
return indexingService;
}
/**
* Getter of- luceneDirectory.
*
* @return the luceneDirectory
*/
public Directory getLuceneDirectory() {
return luceneDirectory;
}
}

View File

@ -0,0 +1,149 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.Objects;
/**
* Authentication token entity.
*
* @author jtremeaux
*/
@Entity
@Table(name = "T_AUTHENTICATION_TOKEN")
public class AuthenticationToken {
/**
* Token.
*/
@Id
@Column(name = "AUT_ID_C", length = 36)
private String id;
/**
* User ID.
*/
@Column(name = "AUT_IDUSER_C", nullable = false, length = 36)
private String userId;
/**
* Remember the user next time (long lasted session).
*/
@Column(name = "AUT_LONGLASTED_B", nullable = false)
private boolean longLasted;
/**
* Token creation date.
*/
@Column(name = "AUT_CREATIONDATE_D", nullable = false)
private Date creationDate;
/**
* Last connection date using this token.
*/
@Column(name = "AUT_LASTCONNECTIONDATE_D")
private Date lastConnectionDate;
/**
* Getter of id.
*
* @return id
*/
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
/**
* Getter of userId.
*
* @return userId
*/
public String getUserId() {
return userId;
}
/**
* Setter of userId.
*
* @param userId userId
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* Getter of longLasted.
*
* @return longLasted
*/
public boolean isLongLasted() {
return longLasted;
}
/**
* Setter of longLasted.
*
* @param longLasted longLasted
*/
public void setLongLasted(boolean longLasted) {
this.longLasted = longLasted;
}
/**
* Getter of creationDate.
*
* @return creationDate
*/
public Date getCreationDate() {
return creationDate;
}
/**
* Setter of creationDate.
*
* @param creationDate creationDate
*/
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
/**
* Getter of lastConnectionDate.
*
* @return lastConnectionDate
*/
public Date getLastConnectionDate() {
return lastConnectionDate;
}
/**
* Setter of lastConnectionDate.
*
* @param lastConnectionDate lastConnectionDate
*/
public void setLastConnectionDate(Date lastConnectionDate) {
this.lastConnectionDate = lastConnectionDate;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", "**hidden**")
.add("userId", userId)
.add("longLasted", longLasted)
.toString();
}
}

View File

@ -0,0 +1,49 @@
package com.sismics.docs.core.model.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.Objects;
/**
* Base function entity.
*
* @author jtremeaux
*/
@Entity
@Table(name = "T_BASE_FUNCTION")
public class BaseFunction {
/**
* Base function ID (ex: "ADMIN").
*/
@Id
@Column(name = "BAF_ID_C", length = 10)
private String id;
/**
* Getter of id.
*
* @return id
*/
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.toString();
}
}

View File

@ -0,0 +1,77 @@
package com.sismics.docs.core.model.jpa;
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.Objects;
import com.sismics.docs.core.constant.ConfigType;
/**
* Configuration parameter entity.
*
* @author jtremeaux
*/
@Entity
@Table(name = "T_CONFIG")
public class Config {
/**
* Configuration parameter ID.
*/
@Id
@Column(name = "CFG_ID_C", length = 50)
@Enumerated(EnumType.STRING)
private ConfigType id;
/**
* Configuration parameter value.
*/
@Column(name = "CFG_VALUE_C", length = 250)
private String value;
/**
* Getter of id.
*
* @return id
*/
public ConfigType getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(ConfigType id) {
this.id = id;
}
/**
* Getter of value.
*
* @return value
*/
public String getValue() {
return value;
}
/**
* Setter of value.
*
* @param value value
*/
public void setValue(String value) {
this.value = value;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.toString();
}
}

View File

@ -0,0 +1,171 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.Objects;
/**
* Document entity.
*
* @author bgamard
*/
@Entity
@Table(name = "T_DOCUMENT")
public class Document {
/**
* Document ID.
*/
@Id
@Column(name = "DOC_ID_C", length = 36)
private String id;
/**
* User ID.
*/
@Column(name = "DOC_IDUSER_C", nullable = false, length = 36)
private String userId;
/**
* Title.
*/
@Column(name = "DOC_TITLE_C", length = 100)
private String title;
/**
* Description.
*/
@Column(name = "DOC_DESCRIPTION_C", length = 4000)
private String description;
/**
* Creation date.
*/
@Column(name = "DOC_CREATEDATE_D", nullable = false)
private Date createDate;
/**
* Deletion date.
*/
@Column(name = "DOC_DELETEDATE_D")
private Date deleteDate;
/**
* Getter of id.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
/**
* Getter of userId.
*
* @return the userId
*/
public String getUserId() {
return userId;
}
/**
* Setter of userId.
*
* @param userId userId
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* Getter of title.
*
* @return the title
*/
public String getTitle() {
return title;
}
/**
* Setter of title.
*
* @param title title
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Getter of description.
*
* @return the description
*/
public String getDescription() {
return description;
}
/**
* Setter of description.
*
* @param description description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* Getter of createDate.
*
* @return the createDate
*/
public Date getCreateDate() {
return createDate;
}
/**
* Setter of createDate.
*
* @param createDate createDate
*/
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
/**
* Getter of deleteDate.
*
* @return the deleteDate
*/
public Date getDeleteDate() {
return deleteDate;
}
/**
* Setter of deleteDate.
*
* @param deleteDate deleteDate
*/
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.toString();
}
}

View File

@ -0,0 +1,147 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.Objects;
/**
* File entity.
*
* @author bgamard
*/
@Entity
@Table(name = "T_FILE")
public class File {
/**
* File ID.
*/
@Id
@Column(name = "FIL_ID_C", length = 36)
private String id;
/**
* Document ID.
*/
@Column(name = "FIL_IDDOC_C", nullable = false, length = 36)
private String documentId;
/**
* Document ID.
*/
@Column(name = "FIL_MIMETYPE_C", length = 100)
private String mimeType;
/**
* Creation date.
*/
@Column(name = "FIL_CREATEDATE_D", nullable = false)
private Date createDate;
/**
* Deletion date.
*/
@Column(name = "FIL_DELETEDATE_D")
private Date deleteDate;
/**
* Getter of id.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
/**
* Getter of documentId.
*
* @return the documentId
*/
public String getDocumentId() {
return documentId;
}
/**
* Setter of documentId.
*
* @param documentId documentId
*/
public void setDocumentId(String documentId) {
this.documentId = documentId;
}
/**
* Getter of mimeType.
*
* @return the mimeType
*/
public String getMimeType() {
return mimeType;
}
/**
* Setter of mimeType.
*
* @param mimeType mimeType
*/
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
/**
* Getter of createDate.
*
* @return the createDate
*/
public Date getCreateDate() {
return createDate;
}
/**
* Setter of createDate.
*
* @param createDate createDate
*/
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
/**
* Getter of deleteDate.
*
* @return the deleteDate
*/
public Date getDeleteDate() {
return deleteDate;
}
/**
* Setter of deleteDate.
*
* @param deleteDate deleteDate
*/
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.toString();
}
}

View File

@ -0,0 +1,49 @@
package com.sismics.docs.core.model.jpa;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.Objects;
/**
* Locale entity.
*
* @author jtremeaux
*/
@Entity
@Table(name = "T_LOCALE")
public class Locale {
/**
* Locale ID (ex: fr_FR).
*/
@Id
@Column(name = "LOC_ID_C", length = 10)
private String id;
/**
* Getter of id.
*
* @return id
*/
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.toString();
}
}

View File

@ -0,0 +1,124 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.Objects;
/**
* Role (set of base functions).
*
* @author jtremeaux
*/
@Entity
@Table(name = "T_ROLE")
public class Role {
/**
* Role ID.
*/
@Id
@Column(name = "ROL_ID_C", length = 36)
private String id;
/**
* Role name.
*/
@Column(name = "ROL_NAME_C", nullable = false, length = 50)
private String name;
/**
* Creation date.
*/
@Column(name = "ROL_CREATEDATE_D", nullable = false)
private Date createDate;
/**
* Deletion date.
*/
@Column(name = "ROL_DELETEDATE_D")
private Date deleteDate;
/**
* Getter of id.
*
* @return id
*/
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
/**
* Getter of name.
*
* @return name
*/
public String getName() {
return name;
}
/**
* Setter of name.
*
* @param name name
*/
public void setName(String name) {
this.name = name;
}
/**
* Getter of createDate.
*
* @return createDate
*/
public Date getCreateDate() {
return createDate;
}
/**
* Setter of createDate.
*
* @param createDate createDate
*/
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
/**
* Getter of deleteDate.
*
* @return deleteDate
*/
public Date getDeleteDate() {
return deleteDate;
}
/**
* Setter of deleteDate.
*
* @param deleteDate deleteDate
*/
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.add("name", name)
.toString();
}
}

View File

@ -0,0 +1,149 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.Objects;
/**
* Role base function.
*
* @author jtremeaux
*/
@Entity
@Table(name = "T_ROLE_BASE_FUNCTION")
public class RoleBaseFunction {
/**
* Role base function ID.
*/
@Id
@Column(name = "RBF_ID_C", length = 36)
private String id;
/**
* Role ID.
*/
@Column(name = "RBF_IDROLE_C", nullable = false, length = 36)
private String roleId;
/**
* Base function ID.
*/
@Column(name = "RBF_IDBASEFUNCTION_C", nullable = false, length = 36)
private String baseFunctionId;
/**
* Creation date.
*/
@Column(name = "RBF_CREATEDATE_D", nullable = false)
private Date createDate;
/**
* Deletion date.
*/
@Column(name = "RBF_DELETEDATE_D")
private Date deleteDate;
/**
* Getter of id.
*
* @return id
*/
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
/**
* Getter of roleId.
*
* @return roleId
*/
public String getRoleId() {
return roleId;
}
/**
* Setter of roleId.
*
* @param roleId roleId
*/
public void setRoleId(String roleId) {
this.roleId = roleId;
}
/**
* Getter of baseFunctionId.
*
* @return baseFunctionId
*/
public String getBaseFunctionId() {
return baseFunctionId;
}
/**
* Setter of baseFunctionId.
*
* @param baseFunctionId baseFunctionId
*/
public void setBaseFunctionId(String baseFunctionId) {
this.baseFunctionId = baseFunctionId;
}
/**
* Getter of createDate.
*
* @return createDate
*/
public Date getCreateDate() {
return createDate;
}
/**
* Setter of createDate.
*
* @param createDate createDate
*/
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
/**
* Getter of deleteDate.
*
* @return deleteDate
*/
public Date getDeleteDate() {
return deleteDate;
}
/**
* Setter of deleteDate.
*
* @param deleteDate deleteDate
*/
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.add("userId", roleId)
.add("baseFunctionId", baseFunctionId)
.toString();
}
}

View File

@ -0,0 +1,268 @@
package com.sismics.docs.core.model.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import com.google.common.base.Objects;
/**
* User entity.
*
* @author jtremeaux
*/
@Entity
@Table(name = "T_USER")
public class User {
/**
* User ID.
*/
@Id
@Column(name = "USE_ID_C", length = 36)
private String id;
/**
* Locale ID.
*/
@Column(name = "USE_IDLOCALE_C", nullable = false, length = 10)
private String localeId;
/**
* Role ID.
*/
@Column(name = "USE_IDROLE_C", nullable = false, length = 36)
private String roleId;
/**
* User's username.
*/
@Column(name = "USE_USERNAME_C", nullable = false, length = 50)
private String username;
/**
* User's password.
*/
@Column(name = "USE_PASSWORD_C", nullable = false, length = 100)
private String password;
/**
* Email address.
*/
@Column(name = "USE_EMAIL_C", nullable = false, length = 100)
private String email;
/**
* Theme.
*/
@Column(name = "USE_THEME_C", nullable = false, length = 100)
private String theme;
/**
* True if the user hasn't dismissed the first connection screen.
*/
@Column(name = "USE_FIRSTCONNECTION_B", nullable = false)
private boolean firstConnection;
/**
* Creation date.
*/
@Column(name = "USE_CREATEDATE_D", nullable = false)
private Date createDate;
/**
* Deletion date.
*/
@Column(name = "USE_DELETEDATE_D")
private Date deleteDate;
/**
* Getter of id.
*
* @return id
*/
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
/**
* Getter of localeId.
*
* @return localeId
*/
public String getLocaleId() {
return localeId;
}
/**
* Setter of localeId.
*
* @param localeId localeId
*/
public void setLocaleId(String localeId) {
this.localeId = localeId;
}
/**
* Getter of roleId.
*
* @return roleId
*/
public String getRoleId() {
return roleId;
}
/**
* Setter of roleId.
*
* @param roleId roleId
*/
public void setRoleId(String roleId) {
this.roleId = roleId;
}
/**
* Getter of username.
*
* @return username
*/
public String getUsername() {
return username;
}
/**
* Setter of username.
*
* @param username username
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Getter of password.
*
* @return password
*/
public String getPassword() {
return password;
}
/**
* Setter of password.
*
* @param password password
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Getter of email.
*
* @return email
*/
public String getEmail() {
return email;
}
/**
* Setter of email.
*
* @param email email
*/
public void setEmail(String email) {
this.email = email;
}
/**
* Getter of theme.
*
* @return theme
*/
public String getTheme() {
return theme;
}
/**
* Setter of theme.
*
* @param theme theme
*/
public void setTheme(String theme) {
this.theme = theme;
}
/**
* Getter of firstConnection.
*
* @return firstConnection
*/
public boolean isFirstConnection() {
return firstConnection;
}
/**
* Setter of firstConnection.
*
* @param firstConnection firstConnection
*/
public void setFirstConnection(boolean firstConnection) {
this.firstConnection = firstConnection;
}
/**
* Getter of createDate.
*
* @return createDate
*/
public Date getCreateDate() {
return createDate;
}
/**
* Setter of createDate.
*
* @param createDate createDate
*/
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
/**
* Getter of deleteDate.
*
* @return deleteDate
*/
public Date getDeleteDate() {
return deleteDate;
}
/**
* Setter of deleteDate.
*
* @param deleteDate deleteDate
*/
public void setDeleteDate(Date deleteDate) {
this.deleteDate = deleteDate;
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("id", id)
.add("username", username)
.toString();
}
}

View File

@ -0,0 +1,97 @@
package com.sismics.docs.core.service;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.store.SimpleFSLockFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.util.concurrent.AbstractScheduledService;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.util.DirectoryUtil;
import com.sismics.docs.core.util.TransactionUtil;
/**
* Indexing service.
*
* @author bgamard
*/
public class IndexingService extends AbstractScheduledService {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(IndexingService.class);
/**
* Lucene directory.
*/
private Directory directory;
/**
* Lucene storage config.
*/
private String luceneStorageConfig;
public IndexingService(String luceneStorageConfig) {
this.luceneStorageConfig = luceneStorageConfig;
}
@Override
protected void startUp() {
// RAM directory storage by default
if (luceneStorageConfig == null || luceneStorageConfig.equals(Constants.LUCENE_DIRECTORY_STORAGE_RAM)) {
directory = new RAMDirectory();
log.info("Using RAM Lucene storage");
} else if (luceneStorageConfig.equals(Constants.LUCENE_DIRECTORY_STORAGE_FILE)) {
File luceneDirectory = DirectoryUtil.getLuceneDirectory();
log.info("Using file Lucene storage: {}", luceneDirectory);
try {
directory = new SimpleFSDirectory(luceneDirectory, new SimpleFSLockFactory());
} catch (IOException e) {
log.error("Error initializing Lucene index", e);
}
}
}
@Override
protected void shutDown() {
Directory luceneIndex = AppContext.getInstance().getLuceneDirectory();
if (luceneIndex != null) {
try {
luceneIndex.close();
} catch (IOException e) {
log.error("Error closing Lucene index", e);
}
}
}
@Override
protected void runOneIteration() throws Exception {
TransactionUtil.handle(new Runnable() {
@Override
public void run() {
// NOP
}
});
}
@Override
protected Scheduler scheduler() {
return Scheduler.newFixedDelaySchedule(0, 1, TimeUnit.HOURS);
}
/**
* Getter of directory.
*
* @return the directory
*/
public Directory getDirectory() {
return directory;
}
}

View File

@ -0,0 +1,65 @@
package com.sismics.docs.core.util;
import java.util.ResourceBundle;
import com.sismics.docs.core.constant.ConfigType;
import com.sismics.docs.core.dao.jpa.ConfigDao;
import com.sismics.docs.core.model.jpa.Config;
/**
* Configuration parameter utilities.
*
* @author jtremeaux
*/
public class ConfigUtil {
/**
* Returns the textual value of a configuration parameter.
*
* @param configType Type of the configuration parameter
* @return Textual value of the configuration parameter
* @throws IllegalStateException Configuration parameter undefined
*/
public static String getConfigStringValue(ConfigType configType) {
ConfigDao configDao = new ConfigDao();
Config config = configDao.getById(configType);
if (config == null) {
throw new IllegalStateException("Config parameter not found: " + configType);
}
return config.getValue();
}
/**
* Returns the configuration resource bundle.
*
* @return Resource bundle
*/
public static ResourceBundle getConfigBundle() {
return ResourceBundle.getBundle("config");
}
/**
* Returns the integer value of a configuration parameter.
*
* @param configType Type of the configuration parameter
* @return Integer value of the configuration parameter
* @throws IllegalStateException Configuration parameter undefined
*/
public static int getConfigIntegerValue(ConfigType configType) {
String value = getConfigStringValue(configType);
return Integer.parseInt(value);
}
/**
* Returns the boolean value of a configuration parameter.
*
* @param configType Type of the configuration parameter
* @return Boolean value of the configuration parameter
* @throws IllegalStateException Configuration parameter undefined
*/
public static boolean getConfigBooleanValue(ConfigType configType) {
String value = getConfigStringValue(configType);
return Boolean.parseBoolean(value);
}
}

View File

@ -0,0 +1,114 @@
package com.sismics.docs.core.util;
import java.io.File;
import org.apache.commons.lang.StringUtils;
import com.sismics.util.EnvironmentUtil;
/**
* Utilities to gain access to the storage directories used by the application.
*
* @author jtremeaux
*/
public class DirectoryUtil {
/**
* Returns the base data directory.
*
* @return Base data directory
*/
public static File getBaseDataDirectory() {
File baseDataDir = null;
if (EnvironmentUtil.getWebappRoot() != null) {
// We are in a webapp environment
if (StringUtils.isNotBlank(EnvironmentUtil.getDocsHome())) {
// If the docs.home property is set then use it
baseDataDir = new File(EnvironmentUtil.getDocsHome());
if (!baseDataDir.isDirectory()) {
baseDataDir.mkdirs();
}
} else {
// Use the base of the Webapp directory
baseDataDir = new File(EnvironmentUtil.getWebappRoot() + File.separator + "sismicsdocs");
if (!baseDataDir.isDirectory()) {
baseDataDir.mkdirs();
}
}
}
if (baseDataDir == null) {
// Or else (for unit testing), use a temporary directory
baseDataDir = new File(System.getProperty("java.io.tmpdir"));
}
return baseDataDir;
}
/**
* Returns the database directory.
*
* @return Database directory.
*/
public static File getDbDirectory() {
return getDataSubDirectory("db");
}
/**
* Returns the lucene indexes directory.
*
* @return Lucene indexes directory.
*/
public static File getLuceneDirectory() {
return getDataSubDirectory("lucene");
}
/**
* Returns the storage directory.
*
* @return Storage directory.
*/
public static File getStorageDirectory() {
return getDataSubDirectory("storage");
}
/**
* Returns the log directory.
*
* @return Log directory.
*/
public static File getLogDirectory() {
return getDataSubDirectory("log");
}
/**
* Returns the themes directory.
*
* @return Theme directory.
*/
public static File getThemeDirectory() {
String webappRoot = EnvironmentUtil.getWebappRoot();
File themeDir = null;
if (webappRoot != null) {
themeDir = new File(webappRoot + File.separator + "style" + File.separator + "theme");
} else {
themeDir = new File(DirectoryUtil.class.getResource("/style/theme").getFile());
}
if (themeDir != null && themeDir.isDirectory()) {
return themeDir;
}
return null;
}
/**
* Returns a subdirectory of the base data directory
*
* @return Subdirectory
*/
private static File getDataSubDirectory(String subdirectory) {
File baseDataDir = getBaseDataDirectory();
File faviconDirectory = new File(baseDataDir.getPath() + File.separator + subdirectory);
if (!faviconDirectory.isDirectory()) {
faviconDirectory.mkdirs();
}
return faviconDirectory;
}
}

View File

@ -0,0 +1,17 @@
package com.sismics.docs.core.util;
import com.sismics.util.context.ThreadLocalContext;
/**
* Entity manager utils.
*
* @author jtremeaux
*/
public class EntityManagerUtil {
/**
* Flush the entity manager session.
*/
public static void flush() {
ThreadLocalContext.get().getEntityManager().flush();
}
}

View File

@ -0,0 +1,90 @@
package com.sismics.docs.core.util;
import java.io.IOException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.SerialMergeScheduler;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sismics.docs.core.dao.lucene.DocsStandardAnalyzer;
import com.sismics.docs.core.model.context.AppContext;
/**
* Lucene utils.
*
* @author bgamard
*/
public class LuceneUtil {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(LuceneUtil.class);
/**
* Encapsulate a process into a Lucene context.
*
* @param runnable
* @throws IOException
*/
public static void handle(LuceneRunnable runnable) {
// Standard analyzer
IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_42, new DocsStandardAnalyzer(Version.LUCENE_42));
// Merge sequentially, because Lucene writing is already done asynchronously
config.setMergeScheduler(new SerialMergeScheduler());
// Creating index writer
Directory directory = AppContext.getInstance().getLuceneDirectory();
IndexWriter indexWriter = null;
try {
indexWriter = new IndexWriter(directory, config);
} catch (IOException e) {
log.error("Cannot create IndexWriter", e);
}
// Unlock index if needed
try {
if (IndexWriter.isLocked(directory)) {
IndexWriter.unlock(directory);
}
} catch (IOException e) {
log.error("Cannot unlock Lucene directory", e);
}
try {
runnable.run(indexWriter);
} catch (Exception e) {
log.error("Error in running index writing transaction", e);
try {
indexWriter.rollback();
} catch (IOException e1) {
log.error("Cannot rollback index writing transaction", e1);
}
}
try {
indexWriter.close();
} catch (IOException e) {
log.error("Cannot close IndexWriter", e);
}
}
/**
* Lucene runnable.
*
* @author bgamard
*/
public interface LuceneRunnable {
/**
* Code to run in a Lucene context.
*
* @param indexWriter
* @throws Exception
*/
public abstract void run(IndexWriter indexWriter) throws Exception;
}
}

View File

@ -0,0 +1,33 @@
package com.sismics.docs.core.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.util.zip.GZIPInputStream;
/**
* Stream utilities.
*
* @author bgamard
*/
public class StreamUtil {
/**
* Detects if the stream is gzipped, and returns a uncompressed stream according to this.
*
* @param is InputStream
* @return InputStream
* @throws IOException
*/
public static InputStream detectGzip(InputStream is) throws IOException {
PushbackInputStream pb = new PushbackInputStream(is, 2);
byte [] signature = new byte[2];
pb.read(signature);
pb.unread(signature);
if(signature[0] == (byte) GZIPInputStream.GZIP_MAGIC && signature[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8)) {
return new GZIPInputStream(pb);
} else {
return pb;
}
}
}

View File

@ -0,0 +1,93 @@
package com.sismics.docs.core.util;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sismics.util.context.ThreadLocalContext;
import com.sismics.util.jpa.EMF;
/**
* Database transaction utils.
*
* @author jtremeaux
*/
public class TransactionUtil {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(TransactionUtil.class);
/**
* Encapsulate a process into a transactionnal context.
*
* @param runnable
*/
public static void handle(Runnable runnable) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
if (em != null) {
// We are already in a transactional context, nothing to do
runnable.run();
return;
}
try {
em = EMF.get().createEntityManager();
} catch (Exception e) {
log.error("Cannot create entity manager", e);
}
ThreadLocalContext context = ThreadLocalContext.get();
context.setEntityManager(em);
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
runnable.run();
} catch (Exception e) {
ThreadLocalContext.cleanup();
log.error("An exception occured, rolling back current transaction", e);
// If an unprocessed error comes up, rollback the transaction
if (em.isOpen()) {
if (em.getTransaction() != null && em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
try {
em.close();
} catch (Exception ce) {
log.error("Error closing entity manager", ce);
}
}
return;
}
ThreadLocalContext.cleanup();
// No error in the current request : commit the transaction
if (em.isOpen()) {
if (em.getTransaction() != null && em.getTransaction().isActive()) {
em.getTransaction().commit();
try {
em.close();
} catch (Exception e) {
log.error("Error closing entity manager", e);
}
}
}
}
/**
* Commits the current transaction, and flushes the changes to the database.
*/
public static void commit() {
EntityTransaction tx = ThreadLocalContext.get().getEntityManager().getTransaction();
tx.commit();
tx.begin();
}
}

View File

@ -0,0 +1,20 @@
package com.sismics.docs.core.util;
import com.sismics.docs.core.model.jpa.User;
/**
* Utilitaires sur les utilisateurs.
*
* @author jtremeaux
*/
public class UserUtil {
/**
* Retourne the user's username.
*
* @param user User
* @return User name
*/
public static String getUserName(User user) {
return user.getUsername();
}
}

View File

@ -0,0 +1,95 @@
package com.sismics.docs.core.util.jpa;
import java.util.List;
/**
* Paginated list.
*
* @author jtremeaux
*/
public class PaginatedList<T> {
/**
* Size of a page.
*/
private int limit;
/**
* Offset of the page (in number of records).
*/
private int offset;
/**
* Total number of records.
*/
private int resultCount;
/**
* List of records of the current page.
*/
private List<T> resultList;
/**
* Constructor of PaginatedList.
*
* @param pageSize Page size
* @param offset Offset
*/
public PaginatedList(int pageSize, int offset) {
this.limit = pageSize;
this.offset = offset;
}
/**
* Getter of resultCount.
*
* @return resultCount
*/
public int getResultCount() {
return resultCount;
}
/**
* Setter of resultCount.
*
* @param resultCount resultCount
*/
public void setResultCount(int resultCount) {
this.resultCount = resultCount;
}
/**
* Getter of resultList.
*
* @return resultList
*/
public List<T> getResultList() {
return resultList;
}
/**
* Setter of resultList.
*
* @param resultList resultList
*/
public void setResultList(List<T> resultList) {
this.resultList = resultList;
}
/**
* Getter of limit.
*
* @return limit
*/
public int getLimit() {
return limit;
}
/**
* Getter of offset.
*
* @return offset
*/
public int getOffset() {
return offset;
}
}

View File

@ -0,0 +1,119 @@
package com.sismics.docs.core.util.jpa;
import java.util.List;
import javax.persistence.Query;
/**
* Utilities for paginated lists.
*
* @author jtremeaux
*/
public class PaginatedLists {
/**
* Default size of a page.
*/
private static final int DEFAULT_PAGE_SIZE = 10;
/**
* Maximum size of a page.
*/
private static final int MAX_PAGE_SIZE = 100;
/**
* Constructs a paginated list.
*
* @param pageSize Size of the page
* @param offset Offset of the page
* @return Paginated list
*/
public static <E> PaginatedList<E> create(Integer pageSize, Integer offset) {
if (pageSize == null) {
pageSize = DEFAULT_PAGE_SIZE;
}
if (offset == null) {
offset = 0;
}
if (pageSize > MAX_PAGE_SIZE) {
pageSize = MAX_PAGE_SIZE;
}
return new PaginatedList<E>(pageSize, offset);
}
/**
* Constructs a paginated list with default parameters.
*
* @return Paginated list
*/
public static <E> PaginatedList<E> create() {
return create(null, null);
}
/**
* Executes a native count(*) request to count the number of results.
*
* @param paginatedList Paginated list object containing parameters, and into which results are added by side effects
* @param queryParam Query parameters
*/
public static <E> void executeCountQuery(PaginatedList<E> paginatedList, QueryParam queryParam) {
StringBuilder sb = new StringBuilder("select count(*) as result_count from (");
sb.append(queryParam.getQueryString());
sb.append(") as t1");
QueryParam countQueryParam = new QueryParam(sb.toString(), queryParam.getParameterMap());
Query q = QueryUtil.getNativeQuery(countQueryParam);
Number resultCount = (Number) q.getSingleResult();
paginatedList.setResultCount(resultCount.intValue());
}
/**
* Executes a query and returns the data of the currunt page.
*
* @param em EntityManager
* @param paginatedList Paginated list object containing parameters, and into which results are added by side effects
* @param queryParam Query parameters
* @return List of results
*/
@SuppressWarnings("unchecked")
private static <E> List<Object[]> executeResultQuery(PaginatedList<E> paginatedList, QueryParam queryParam) {
Query q = QueryUtil.getNativeQuery(queryParam);
q.setFirstResult(paginatedList.getOffset());
q.setMaxResults(paginatedList.getLimit());
return q.getResultList();
}
/**
* Executes a paginated request with 2 native queries (one to count the number of results, and one to return the page).
*
* @param paginatedList Paginated list object containing parameters, and into which results are added by side effects
* @param queryParam Query parameters
* @return List of results
*/
public static <E> List<Object[]> executePaginatedQuery(PaginatedList<E> paginatedList, QueryParam queryParam) {
executeCountQuery(paginatedList, queryParam);
return executeResultQuery(paginatedList, queryParam);
}
/**
* Executes a paginated request with 2 native queries (one to count the number of results, and one to return the page).
*
* @param paginatedList Paginated list object containing parameters, and into which results are added by side effects
* @param queryParam Query parameters
* @param sortCriteria Sort criteria
* @return List of results
*/
public static <E> List<Object[]> executePaginatedQuery(PaginatedList<E> paginatedList, QueryParam queryParam, SortCriteria sortCriteria) {
StringBuilder sb = new StringBuilder(queryParam.getQueryString());
sb.append(" order by c");
sb.append(sortCriteria.getColumn());
sb.append(sortCriteria.isAsc() ? " asc" : " desc");
QueryParam sortedQueryParam = new QueryParam(sb.toString(), queryParam.getParameterMap());
executeCountQuery(paginatedList, sortedQueryParam);
return executeResultQuery(paginatedList, sortedQueryParam);
}
}

View File

@ -0,0 +1,51 @@
package com.sismics.docs.core.util.jpa;
import java.util.Map;
/**
* Query parameters.
*
* @author jtremeaux
*/
public class QueryParam {
/**
* Query string.
*/
private String queryString;
/**
* Query parameters.
*/
private Map<String, Object> parameterMap;
/**
* Constructor of QueryParam.
*
* @param queryString Query string
* @param parameterMap Query parameters
*/
public QueryParam(String queryString, Map<String, Object> parameterMap) {
this.queryString = queryString;
this.parameterMap = parameterMap;
}
/**
* Getter of queryString.
*
* @return queryString
*/
public String getQueryString() {
return queryString;
}
/**
* Getter of parameterMap.
*
* @return parameterMap
*/
public Map<String, Object> getParameterMap() {
return parameterMap;
}
}

View File

@ -0,0 +1,31 @@
package com.sismics.docs.core.util.jpa;
import java.util.Map.Entry;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import com.sismics.util.context.ThreadLocalContext;
/**
* Query utilities.
*
* @author jtremeaux
*/
public class QueryUtil {
/**
* Creates a native query from the query parameters.
*
* @param queryParam Query parameters
* @return Native query
*/
public static Query getNativeQuery(QueryParam queryParam) {
EntityManager em = ThreadLocalContext.get().getEntityManager();
Query query = em.createNativeQuery(queryParam.getQueryString());
for (Entry<String, Object> entry : queryParam.getParameterMap().entrySet()) {
query.setParameter(entry.getKey(), entry.getValue());
}
return query;
}
}

View File

@ -0,0 +1,48 @@
package com.sismics.docs.core.util.jpa;
/**
* Sort criteria of a query.
*
* @author jtremeaux
*/
public class SortCriteria {
/**
* Index of the column to sort (first is 0).
*/
private int column;
/**
* Sort in increasing order (or else decreasing).
*/
private boolean asc = true;
/**
* Constructor of sortCriteria.
*/
public SortCriteria(Integer column, Boolean asc) {
if (column != null) {
this.column = column;
}
if (asc != null) {
this.asc = asc;
}
}
/**
* Getter of column.
*
* @return column
*/
public int getColumn() {
return column;
}
/**
* Getter of asc.
*
* @return asc
*/
public boolean isAsc() {
return asc;
}
}

View File

@ -0,0 +1,97 @@
package com.sismics.docs.core.util.math;
/**
* Classe utilitaire pour les calculs
*
* @author bgamard
*
*/
public class MathUtil {
/**
* Arrondi à 2 décimales près
*
* @param d Nombre à arrondir
* @return Nombre arrondi
*/
public static Double round(Double d) {
return Math.round(d * 100.0) / 100.0;
}
/**
* Contraint une valeur entre min et max.
*
* @param value Valeur
* @param min Minimum
* @param max Maximum
* @return Valeur contrainte
*/
public static double clip(double value, double min, double max) {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
/**
* Interpole une valeur entre deux points.
*
* @param x Valeur à interpoler
* @param x1 Point 1 (x)
* @param y1 Point 1 (y)
* @param x2 Point 2 (x)
* @param y2 Point 2 (y)
* @return Valeur interpolée
*/
public static double interpolate(double x, double x1, double y1, double x2, double y2) {
double alpha = (x - x1) / (x2 - x1);
return y1 * (1 - alpha) + y2 * alpha;
}
/**
* Retourne un Double depuis un Number.
*
* @param number Number
* @return Double
*/
public static Double getDoubleFromNumber(Number number) {
if (number == null) {
return null;
}
return number.doubleValue();
}
/**
* Retourne un Integer depuis un Number.
*
* @param number Number
* @return Integer
*/
public static Integer getIntegerFromNumber(Number number) {
if (number == null) {
return null;
}
return number.intValue();
}
/**
* Retourne un Long depuis un Number.
*
* @param number Number
* @return Long
*/
public static Long getLongFromNumber(Number number) {
if (number == null) {
return null;
}
return number.longValue();
}
}

View File

@ -0,0 +1,227 @@
package com.sismics.util;
import java.util.Map.Entry;
import com.google.common.collect.ImmutableMap;
/**
* Date utilities.
*
* @author jtremeaux
*/
public class DateUtil {
private final static ImmutableMap<String, String> TIMEZONE_CODE_MAP = new ImmutableMap.Builder<String, String>()
.put(" ACDT", " +10:30")
.put(" ACST", " +09:30")
.put(" ACT", " +08")
.put(" ADT", " 03")
.put(" AEDT", " +11")
.put(" AEST", " +10")
.put(" AFT", " +04:30")
.put(" AKDT", " 08")
.put(" AKST", " 09")
.put(" AMST", " +05")
.put(" AMT", " +04")
.put(" ART", " 03")
// .put(" AST", " +03")
.put(" AST", " 04")
.put(" AWDT", " +09")
.put(" AWST", " +08")
.put(" AZOST", " 01")
.put(" AZT", " +04")
.put(" BDT", " +08")
.put(" BIOT", " +06")
.put(" BIT", " 12")
.put(" BOT", " 04")
.put(" BRT", " 03")
// .put(" BST", " +06")
.put(" BST", " +01")
.put(" BTT", " +06")
.put(" CAT", " +02")
.put(" CCT", " +06:30")
.put(" CDT", " 05")
// .put(" CDT", " 04")
.put(" CEDT", " +02")
.put(" CEST", " +02")
.put(" CET", " +01")
.put(" CHADT", " +13:45")
.put(" CHAST", " +12:45")
.put(" CHOT", " 08")
.put(" ChST", " +10")
.put(" CHUT", " +10")
.put(" CIST", " 08")
.put(" CIT", " +08")
.put(" CKT", " 10")
.put(" CLST", " 03")
.put(" CLT", " 04")
.put(" COST", " 04")
.put(" COT", " 05")
.put(" CST", " 06")
// .put(" CST", " +08")
// .put(" CST", " +09:30")
// .put(" CST", " +10:30")
// .put(" CST", " 05")
.put(" CT", " +08")
.put(" CVT", " 01")
.put(" CWST", " +08:45")
.put(" CXT", " +07")
.put(" DAVT", " +07")
.put(" DDUT", " +10")
.put(" DFT", " +01")
.put(" EASST", " 05")
.put(" EAST", " 06")
.put(" EAT", " +03")
// .put(" ECT", " 04")
.put(" ECT", " 05")
.put(" EDT", " 04")
.put(" EEDT", " +03")
.put(" EEST", " +03")
.put(" EET", " +02")
.put(" EGST", " +00")
.put(" EGT", " 01")
.put(" EIT", " +09")
.put(" EST", " 05")
// .put(" EST", " +10")
.put(" FET", " +03")
.put(" FJT", " +12")
.put(" FKST", " 03")
.put(" FKT", " 04")
.put(" FNT", " 02")
.put(" GALT", " 06")
.put(" GAMT", " 09")
.put(" GET", " +04")
.put(" GFT", " 03")
.put(" GILT", " +12")
.put(" GIT", " 09")
.put(" GMT", " ")
// .put(" GST", " 02")
.put(" GST", " +04")
.put(" GYT", " 04")
.put(" HADT", " 09")
.put(" HAEC", " +02")
.put(" HAST", " 10")
.put(" HKT", " +08")
.put(" HMT", " +05")
.put(" HOVT", " +07")
.put(" HST", " 10")
.put(" ICT", " +07")
.put(" IDT", " +03")
.put(" IOT", " +03")
.put(" IRDT", " +08")
.put(" IRKT", " +09")
.put(" IRST", " +03:30")
.put(" IST", " +05:30")
// .put(" IST", " +01")
// .put(" IST", " +02")
// .put(" JST", " +09")
.put(" KGT", " +06")
.put(" KOST", " +11")
.put(" KRAT", " +07")
.put(" KST", " +09")
.put(" LHST", " +10:30")
// .put(" LHST", " +11")
.put(" LINT", " +14")
.put(" MAGT", " +12")
.put(" MART", " 09:30")
.put(" MAWT", " +05")
.put(" MDT", " 06")
.put(" MET", " +01")
.put(" MEST", " +02")
.put(" MHT", " +12")
.put(" MIST", " +11")
.put(" MIT", " 09:30")
.put(" MMT", " +06:30")
.put(" MSK", " +04")
// .put(" MST", " +08")
.put(" MST", " 07")
// .put(" MST", " +06:30")
.put(" MUT", " +04")
.put(" MVT", " +05")
.put(" MYT", " +08")
.put(" NCT", " +11")
.put(" NDT", " 02:30")
.put(" NFT", " +11:30")
.put(" NPT", " +05:45")
.put(" NST", " 03:30")
.put(" NT", " 03:30")
.put(" NUT", " 11:30")
.put(" NZDT", " +13")
.put(" NZST", " +12")
.put(" OMST", " +06")
.put(" ORAT", " +05")
.put(" PDT", " 07")
.put(" PET", " 05")
.put(" PETT", " +12")
.put(" PGT", " +10")
.put(" PHOT", " +13")
.put(" PHT", " +08")
.put(" PKT", " +05")
.put(" PMDT", " 02")
.put(" PMST", " 03")
.put(" PONT", " +11")
.put(" PST", " 08")
// .put(" PST", " +08")
.put(" RET", " +04")
.put(" ROTT", " 03")
.put(" SAKT", " +11")
.put(" SAMT", " +04")
.put(" SAST", " +02")
.put(" SBT", " +11")
.put(" SCT", " +04")
.put(" SGT", " +08")
.put(" SLT", " +05:30")
.put(" SRT", " 03")
.put(" SST", " 11")
// .put(" SST", " +08")
.put(" SYOT", " +03")
.put(" TAHT", " 10")
.put(" THA", " +07")
.put(" TFT", " +05")
.put(" TJT", " +05")
.put(" TKT", " +14")
.put(" TLT", " +09")
.put(" TMT", " +05")
.put(" TOT", " +13")
.put(" TVT", " +12")
.put(" UCT", " ")
.put(" ULAT", " +08")
.put(" UTC", " ")
.put(" UYST", " 02")
.put(" UYT", " 03")
.put(" UZT", " +05")
.put(" VET", " 04:30")
.put(" VLAT", " +10")
.put(" VOLT", " +04")
.put(" VOST", " +06")
.put(" VUT", " +11")
.put(" WAKT", " +12")
.put(" WAST", " +02")
.put(" WAT", " +01")
.put(" WEDT", " +01")
.put(" WEST", " +01")
.put(" WET", " ")
.put(" WST", " +08")
.put(" YAKT", " +09")
.put(" YEKT", " +05")
.build();
/**
* Try to guess the timezone code, and replace it with a timezone offset.
* Note: JodaTime can guess a few codes already, they didn't include all codes since they are not standardized.
* This method should only be used in last resort.
*
* @param date Formated date, supposedly ending with a timezone code
* @return Date with the code replaced by its offset if there is a match
*/
public static String guessTimezoneOffset(String date) {
for (Entry<String, String> entry : TIMEZONE_CODE_MAP.entrySet()) {
String code = entry.getKey();
String offset = entry.getValue();
if (date.endsWith(code)) {
return date.substring(0, date.length() - code.length()) + offset;
}
}
return date;
}
}

View File

@ -0,0 +1,106 @@
package com.sismics.util;
/**
* Environment properties utilities.
*
* @author jtremeaux
*/
public class EnvironmentUtil {
private static String OS = System.getProperty("os.name").toLowerCase();
private static String TEST_ENV = System.getProperty("test");
private static String WINDOWS_APPDATA = System.getenv("APPDATA");
private static String MAC_OS_USER_HOME = System.getProperty("user.home");
private static String DOCS_HOME = System.getProperty("docs.home");
/**
* Web application root.
*/
private static String webappRoot;
/**
* Returns true if running under Microsoft Windows.
*
* @return Running under Microsoft Windows
*/
public static boolean isWindows() {
return OS.indexOf("win") >= 0;
}
/**
* Returns true if running under Mac OS.
*
* @return Running under Mac OS
*/
public static boolean isMacOs() {
return OS.indexOf("mac") >= 0;
}
/**
* Returns true if running under UNIX.
*
* @return Running under UNIX
*/
public static boolean isUnix() {
return OS.indexOf("nix") >= 0 || OS.indexOf("nux") >= 0 || OS.indexOf("aix") > 0;
}
/**
* Returns true if we are in a unit testing environment.
*
* @return Unit testing environment
*/
public static boolean isUnitTest() {
return webappRoot == null ||
TEST_ENV != null && "true".equals(TEST_ENV);
}
/**
* Returns the MS Windows AppData directory of this user.
*
* @return AppData directory
*/
public static String getWindowsAppData() {
return WINDOWS_APPDATA;
}
/**
* Returns the Mac OS home directory of this user.
*
* @return Home directory
*/
public static String getMacOsUserHome() {
return MAC_OS_USER_HOME;
}
/**
* Returns the home directory of DOCS (e.g. /var/docs).
*
* @return Home directory
*/
public static String getDocsHome() {
return DOCS_HOME;
}
/**
* Getter of webappRoot.
*
* @return webappRoot
*/
public static String getWebappRoot() {
return webappRoot;
}
/**
* Setter of webappRoot.
*
* @param webappRoot webappRoot
*/
public static void setWebappRoot(String webappRoot) {
EnvironmentUtil.webappRoot = webappRoot;
}
}

View File

@ -0,0 +1,93 @@
package com.sismics.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* HTTP request utilities.
*
* @author jtremeaux
*/
public class HttpUtil {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
/**
* Loads the content of an URL into a string.
*
* @param url URL to load
* @return Contents of the resource
*/
public static String readUrlIntoString(URL url) {
URLConnection connection;
BufferedReader in = null;
try {
connection = url.openConnection();
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder sb = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
sb.append(inputLine);
}
return sb.toString();
} catch (IOException e) {
if (log.isErrorEnabled()) {
log.error("Error reading URL", e);
}
return null;
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
// NOP
}
}
}
}
public static String postUrl(URL url, String data) throws IOException {
OutputStreamWriter wr = null;
BufferedReader rd = null;
try {
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(data);
wr.flush();
// Get the response
rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = rd.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
} finally {
if (wr != null) {
try {
wr.close();
} catch (IOException e) {
// NOP
}
}
if (rd != null) {
try {
rd.close();
} catch (IOException e) {
// NOP
}
}
}
}
}

View File

@ -0,0 +1,111 @@
package com.sismics.util;
import java.text.MessageFormat;
import org.codehaus.jackson.JsonNode;
/**
* JSON validation utilities.
*
* @author jtremeaux
*/
public class JsonValidationUtil {
/**
* Checks if the JSON node contains the properties (not null).
*
* @param s JSON node to check
* @param name Name of the property
* @throws JSONException
*/
public static void validateJsonRequired(JsonNode n, String name) throws Exception {
if (!n.has(name)) {
throw new Exception(MessageFormat.format("{0} must be set", name));
}
}
/**
* Checks that the property is a JSON object.
*
* @param s JSON node to check
* @param name Name of the property
* @param required Property required
* @throws JSONException
*/
public static void validateJsonObject(JsonNode n, String name, boolean required) throws Exception {
if (required && !n.has(name)) {
throw new Exception(MessageFormat.format("{0} must be set", name));
}
if (n.has(name) && !n.path(name).isObject()) {
throw new Exception(MessageFormat.format("{0} must be a JSON object", name));
}
}
/**
* Checks that the property is a number.
*
* @param s JSON node to check
* @param name Name of the property
* @param required Property required
* @throws JSONException
*/
public static void validateJsonNumber(JsonNode n, String name, boolean required) throws Exception {
if (required && !n.has(name)) {
throw new Exception(MessageFormat.format("{0} must be set", name));
}
if (n.has(name) && !n.path(name).isNumber()) {
throw new Exception(MessageFormat.format("{0} must be a number", name));
}
}
/**
* Checks that the property is a long.
*
* @param s JSON node to check
* @param name Name of the property
* @param required Property required
* @throws JSONException
*/
public static void validateJsonLong(JsonNode n, String name, boolean required) throws Exception {
if (required && !n.has(name)) {
throw new Exception(MessageFormat.format("{0} must be set", name));
}
if (n.has(name) && !n.path(name).isLong()) {
throw new Exception(MessageFormat.format("{0} must be a long", name));
}
}
/**
* Checks that the property is a string.
*
* @param s JSON node to check
* @param name Name of the property
* @param required Property required
* @throws JSONException
*/
public static void validateJsonString(JsonNode n, String name, boolean required) throws Exception {
if (required && !n.has(name)) {
throw new Exception(MessageFormat.format("{0} must be set", name));
}
if (n.has(name) && !n.path(name).isTextual()) {
throw new Exception(MessageFormat.format("{0} must be a string", name));
}
}
/**
* Checks that the property is an array.
*
* @param s JSON node to check
* @param name Name of the property
* @param required Property required
* @throws JSONException
*/
public static void validateJsonArray(JsonNode n, String name, boolean required) throws Exception {
if (required && !n.has(name)) {
throw new Exception(MessageFormat.format("{0} must be set", name));
}
if (n.has(name) && !n.path(name).isArray()) {
throw new Exception(MessageFormat.format("{0} must be an array", name));
}
}
}

View File

@ -0,0 +1,63 @@
package com.sismics.util;
import java.util.Locale;
import org.apache.commons.lang.StringUtils;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.jpa.LocaleDao;
/**
* Locale utilities.
*
* @author jtremeaux
*/
public class LocaleUtil {
/**
* Returns the locale from its language / country code (ex: fr_FR).
*
* @param localeCode Locale code
* @return Locate instance
*/
public static final Locale getLocale(String localeCode) {
String[] localeCodeArray = localeCode.split("_");
String language = localeCodeArray[0];
String country = "";
String variant = "";
if (localeCodeArray.length >= 2) {
country = localeCodeArray[1];
}
if (localeCodeArray.length >= 3) {
variant = localeCodeArray[2];
}
return new Locale(language, country, variant);
}
/**
* Extracts the ID of the locale from the HTTP Accept-Language header.
*
* @param HTTP header
* @return Locale ID
*/
public static String getLocaleIdFromAcceptLanguage(String acceptLanguageHeader) {
String localeId = null;
if (StringUtils.isNotBlank(acceptLanguageHeader)) {
acceptLanguageHeader = acceptLanguageHeader.replaceAll("-", "_");
localeId = acceptLanguageHeader.split(",")[0];
}
if (StringUtils.isNotBlank(localeId)) {
LocaleDao localeDao = new LocaleDao();
com.sismics.docs.core.model.jpa.Locale locale = localeDao.getById(localeId);
if (locale != null) {
localeId = locale.getId();
} else {
// The client provided an unknown locale
localeId = Constants.DEFAULT_LOCALE_ID;
}
}
if (StringUtils.isBlank(localeId)) {
localeId = Constants.DEFAULT_LOCALE_ID;
}
return localeId;
}
}

View File

@ -0,0 +1,43 @@
package com.sismics.util;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
/**
* Message utilities.
*
* @author jtremeaux
*/
public class MessageUtil {
/**
* Returns a message formated in the specified locale.
* Returns **key** if no message is set for this key.
*
* @param locale Locale
* @param key Message key
* @param args Arguments of the message
* @return Formated message
*/
public static String getMessage(Locale locale, String key, Object... args) {
ResourceBundle resources = ResourceBundle.getBundle("messages", locale);
String message = null;
try {
message = resources.getString(key);
} catch (MissingResourceException e) {
message = "**" + key + "**";
}
return MessageFormat.format(message, args);
}
/**
* Returns the resource bundle containing messages for the locale.
*
* @param locale Locale to use
* @return Resource bundle
*/
public static ResourceBundle getMessage(Locale locale) {
return ResourceBundle.getBundle("messages", locale);
}
}

View File

@ -0,0 +1,101 @@
package com.sismics.util;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import com.google.common.collect.Lists;
/**
* Resource utilities.
*
* @author jtremeaux
*/
public class ResourceUtil {
/**
* List files inside a directory. The path can be a directory on the filesystem, or inside a JAR.
*
* @param clazz Class
* @param path Path
* @param filter Filter
* @return List of files
* @throws URISyntaxException
* @throws IOException
*/
public static List<String> list(Class<?> clazz, String path, FilenameFilter filter) throws URISyntaxException, IOException {
// Path is a directory on the filesystem
URL dirUrl = clazz.getResource(path);
if (dirUrl != null && dirUrl.getProtocol().equals("file")) {
return Arrays.asList(new File(dirUrl.toURI()).list(filter));
}
// Path is a directory inside the same JAR as clazz
if (dirUrl == null) {
String className = clazz.getName().replace(".", "/") + ".class";
dirUrl = clazz.getClassLoader().getResource(className);
}
if (dirUrl.getProtocol().equals("jar")) {
if (path.startsWith("/")) {
path = path.substring(1);
}
if (!path.endsWith("/")) {
path = path + "/";
}
// Extract the JAR path
String jarPath = dirUrl.getPath().substring(5, dirUrl.getPath().indexOf("!"));
JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8"));
Enumeration<JarEntry> entries = jar.entries();
Set<String> fileSet = new HashSet<String>();
while (entries.hasMoreElements()) {
// Filter according to the path
String entryName = entries.nextElement().getName();
if (!entryName.startsWith(path)) {
continue;
}
String name = entryName.substring(path.length());
if (!"".equals(name)) {
// If it is a subdirectory, just return the directory name
int checkSubdir = name.indexOf("/");
if (checkSubdir >= 0) {
name = name.substring(0, checkSubdir);
}
if (filter == null || filter.accept(null, name)) {
fileSet.add(name);
}
}
}
return Lists.newArrayList(fileSet);
}
throw new UnsupportedOperationException(MessageFormat.format("Cannot list files for URL {0}", dirUrl));
}
/**
* List files inside a directory. The path can be a directory on the filesystem, or inside a JAR.
*
* @param clazz Class
* @param path Path
* @return List of files
* @throws URISyntaxException
* @throws IOException
*/
public static List<String> list(Class<?> clazz, String path) throws URISyntaxException, IOException {
return list(clazz, path, null);
}
}

View File

@ -0,0 +1,75 @@
package com.sismics.util.context;
import javax.persistence.EntityManager;
/**
* Context associated to a user request, and stored in a ThreadLocal.
*
* @author jtremeaux
*/
public class ThreadLocalContext {
/**
* ThreadLocal to store the context.
*/
public static final ThreadLocal<ThreadLocalContext> threadLocalContext = new ThreadLocal<ThreadLocalContext>();
/**
* Entity manager.
*/
private EntityManager entityManager;
/**
* Private constructor.
*/
private ThreadLocalContext() {
// NOP
}
/**
* Returns an instance of this thread context.
*
* @return Thread local context
*/
public static ThreadLocalContext get() {
ThreadLocalContext context = threadLocalContext.get();
if (context == null) {
context = new ThreadLocalContext();
threadLocalContext.set(context);
}
return context;
}
/**
* Cleans up the instance of this thread context.
*/
public static void cleanup() {
threadLocalContext.set(null);
}
/**
* Returns true only if the entity manager is defined.
*
* @return Condition
*/
public boolean isInTransactionalContext() {
return entityManager != null;
}
/**
* Getter of entityManager.
*
* @return entityManager
*/
public EntityManager getEntityManager() {
return entityManager;
}
/**
* Setter of entityManager.
*
* @param entityManager entityManager
*/
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
}

View File

@ -0,0 +1,231 @@
package com.sismics.util.jpa;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ResourceBundle;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.engine.jdbc.internal.FormatStyle;
import org.hibernate.engine.jdbc.internal.Formatter;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.SqlStatementLogger;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.tool.hbm2ddl.ConnectionHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Strings;
import com.google.common.io.CharStreams;
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.util.ResourceUtil;
/**
* A helper to update the database incrementally.
*
* @author jtremeaux
*/
public abstract class DbOpenHelper {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(DbOpenHelper.class);
private final ConnectionHelper connectionHelper;
private final SqlStatementLogger sqlStatementLogger;
private final List<Exception> exceptions = new ArrayList<Exception>();
private Formatter formatter;
private boolean haltOnError;
private Statement stmt;
public DbOpenHelper(ServiceRegistry serviceRegistry) throws HibernateException {
final JdbcServices jdbcServices = serviceRegistry.getService(JdbcServices.class);
connectionHelper = new SuppliedConnectionProviderConnectionHelper(jdbcServices.getConnectionProvider());
sqlStatementLogger = jdbcServices.getSqlStatementLogger();
formatter = (sqlStatementLogger.isFormat() ? FormatStyle.DDL : FormatStyle.NONE).getFormatter();
}
public void open() {
log.info("Opening database and executing incremental updates");
Connection connection = null;
Writer outputFileWriter = null;
exceptions.clear();
try {
try {
connectionHelper.prepare(true);
connection = connectionHelper.getConnection();
} catch (SQLException sqle) {
exceptions.add(sqle);
log.error("Unable to get database metadata", sqle);
throw sqle;
}
// Check if database is already created
Integer oldVersion = null;
try {
stmt = connection.createStatement();
ResultSet result = stmt.executeQuery("select c.CFG_VALUE_C from T_CONFIG c where c.CFG_ID_C='DB_VERSION'");
if (result.next()) {
String oldVersionStr = result.getString(1);
oldVersion = Integer.parseInt(oldVersionStr);
}
} catch (Exception e) {
if (e.getMessage().contains("object not found")) {
log.info("Unable to get database version: Table T_CONFIG not found");
} else {
log.error("Unable to get database version", e);
}
} finally {
if (stmt != null) {
stmt.close();
stmt = null;
}
}
stmt = connection.createStatement();
if (oldVersion == null) {
// Execute creation script
log.info("Executing initial schema creation script");
onCreate();
oldVersion = 0;
}
// Execute update script
ResourceBundle configBundle = ConfigUtil.getConfigBundle();
Integer currentVersion = Integer.parseInt(configBundle.getString("db.version"));
log.info(MessageFormat.format("Found database version {0}, new version is {1}, executing database incremental update scripts", oldVersion, currentVersion));
onUpgrade(oldVersion, currentVersion);
log.info("Database upgrade complete");
} catch (Exception e) {
exceptions.add(e);
log.error("Unable to complete schema update", e);
} finally {
try {
if (stmt != null) {
stmt.close();
stmt = null;
}
connectionHelper.release();
} catch (Exception e) {
exceptions.add(e);
log.error("Unable to close connection", e);
}
try {
if (outputFileWriter != null) {
outputFileWriter.close();
}
} catch (Exception e) {
exceptions.add(e);
log.error("Unable to close connection", e);
}
}
}
/**
* Execute all upgrade scripts in ascending order for a given version.
*
* @param version Version number
* @throws Exception
*/
protected void executeAllScript(final int version) throws Exception {
List<String> fileNameList = ResourceUtil.list(getClass(), "/db/update/", new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
String versionString = String.format("%03d", version);
return name.matches("dbupdate-" + versionString + "-\\d+\\.sql");
}
});
Collections.sort(fileNameList);
for (String fileName : fileNameList) {
if (log.isInfoEnabled()) {
log.info(MessageFormat.format("Executing script: {0}", fileName));
}
InputStream is = getClass().getResourceAsStream("/db/update/" + fileName);
executeScript(is);
}
}
/**
* Execute a SQL script. All statements must be one line only.
*
* @param inputScript Script to execute
* @throws IOException
* @throws SQLException
*/
protected void executeScript(InputStream inputScript) throws IOException, SQLException {
List<String> lines = CharStreams.readLines(new InputStreamReader(inputScript));
for (String sql : lines) {
if (Strings.isNullOrEmpty(sql) || sql.startsWith("--")) {
continue;
}
String formatted = formatter.format(sql);
try {
log.debug(formatted);
stmt.executeUpdate(formatted);
} catch (SQLException e) {
if (haltOnError) {
if (stmt != null) {
stmt.close();
stmt = null;
}
throw new JDBCException("Error during script execution", e);
}
exceptions.add(e);
if (log.isErrorEnabled()) {
log.error("Error executing SQL statement: {}", sql);
log.error(e.getMessage());
}
}
}
}
public abstract void onCreate() throws Exception;
public abstract void onUpgrade(int oldVersion, int newVersion) throws Exception;
/**
* Returns a List of all Exceptions which occured during the export.
*
* @return A List containig the Exceptions occured during the export
*/
public List<?> getExceptions() {
return exceptions;
}
public void setHaltOnError(boolean haltOnError) {
this.haltOnError = haltOnError;
}
/**
* Format the output SQL statements.
*
* @param format True to format
*/
public void setFormat(boolean format) {
this.formatter = (format ? FormatStyle.DDL : FormatStyle.NONE).getFormatter();
}
}

View File

@ -0,0 +1,111 @@
package com.sismics.util.jpa;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.hibernate.cfg.Environment;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sismics.docs.core.util.DirectoryUtil;
/**
* Entity manager factory.
*
* @author jtremeaux
*/
public final class EMF {
private static final Logger log = LoggerFactory.getLogger(EMF.class);
private static EntityManagerFactory emfInstance;
static {
try {
Map<Object, Object> properties = getEntityManagerProperties();
Environment.verifyProperties(properties);
ConfigurationHelper.resolvePlaceHolders(properties);
ServiceRegistry reg = new ServiceRegistryBuilder().applySettings(properties).buildServiceRegistry();
DbOpenHelper openHelper = new DbOpenHelper(reg) {
@Override
public void onCreate() throws Exception {
executeAllScript(0);
}
@Override
public void onUpgrade(int oldVersion, int newVersion) throws Exception {
for (int version = oldVersion + 1; version <= newVersion; version++) {
executeAllScript(version);
}
}
};
openHelper.open();
emfInstance = Persistence.createEntityManagerFactory("transactions-optional", getEntityManagerProperties());
} catch (Throwable t) {
log.error("Error creating EMF", t);
}
}
private static Map<Object, Object> getEntityManagerProperties() {
// Use properties file if exists
try {
URL hibernatePropertiesUrl = EMF.class.getResource("/hibernate.properties");
if (hibernatePropertiesUrl != null) {
log.info("Configuring EntityManager from hibernate.properties");
InputStream is = hibernatePropertiesUrl.openStream();
Properties properties = new Properties();
properties.load(is);
return properties;
}
} catch (IOException e) {
log.error("Error reading hibernate.properties", e);
}
// Use environment parameters
log.info("Configuring EntityManager from environment parameters");
Map<Object, Object> props = new HashMap<Object, Object>();
props.put("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");
File dbDirectory = DirectoryUtil.getDbDirectory();
String dbFile = dbDirectory.getAbsoluteFile() + File.separator + "docs";
props.put("hibernate.connection.url", "jdbc:hsqldb:file:" + dbFile + ";hsqldb.write_delay=false;shutdown=true");
props.put("hibernate.connection.username", "sa");
props.put("hibernate.hbm2ddl.auto", "none");
props.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
props.put("hibernate.show_sql", "false");
props.put("hibernate.format_sql", "false");
props.put("hibernate.max_fetch_depth", "5");
props.put("hibernate.cache.use_second_level_cache", "false");
return props;
}
/**
* Private constructor.
*/
private EMF() {
}
/**
* Returns an instance of EMF.
*
* @return Instance of EMF
*/
public static EntityManagerFactory get() {
return emfInstance;
}
}

View File

@ -0,0 +1,108 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package com.sismics.util.jpa;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.service.ServiceRegistryBuilder;
import org.hibernate.service.internal.StandardServiceRegistryImpl;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.tool.hbm2ddl.ConnectionHelper;
/**
* A {@link ConnectionHelper} implementation based on an internally
* built and managed {@link ConnectionProvider}.
*
* @author Steve Ebersole
*/
class ManagedProviderConnectionHelper implements ConnectionHelper {
private Properties cfgProperties;
private StandardServiceRegistryImpl serviceRegistry;
private Connection connection;
public ManagedProviderConnectionHelper(Properties cfgProperties) {
this.cfgProperties = cfgProperties;
}
@Override
public void prepare(boolean needsAutoCommit) throws SQLException {
serviceRegistry = createServiceRegistry(cfgProperties);
connection = serviceRegistry.getService(ConnectionProvider.class).getConnection();
if (needsAutoCommit && !connection.getAutoCommit()) {
connection.commit();
connection.setAutoCommit(true);
}
}
private static StandardServiceRegistryImpl createServiceRegistry(Properties properties) {
Environment.verifyProperties(properties);
ConfigurationHelper.resolvePlaceHolders(properties);
return (StandardServiceRegistryImpl) new ServiceRegistryBuilder().applySettings(properties).buildServiceRegistry();
}
@Override
public Connection getConnection() throws SQLException {
return connection;
}
@Override
public void release() throws SQLException {
try {
releaseConnection();
} finally {
releaseServiceRegistry();
}
}
private void releaseConnection() throws SQLException {
if (connection != null) {
try {
new SqlExceptionHelper().logAndClearWarnings(connection);
} finally {
try {
serviceRegistry.getService(ConnectionProvider.class).closeConnection(connection);
} finally {
connection = null;
}
}
}
}
private void releaseServiceRegistry() {
if (serviceRegistry != null) {
try {
serviceRegistry.destroy();
} finally {
serviceRegistry = null;
}
}
}
}

View File

@ -0,0 +1,28 @@
package com.sismics.util.jpa;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.ejb.HibernateEntityManagerFactory;
/**
* Hibernate session utilities.
*
* @author jtremeaux
*/
public final class SessionUtil {
/**
* Private constructor.
*/
private SessionUtil() {
}
/**
* Returns an instance of the current session.
*
* @return Instance of the current session
*/
public static Session getCurrentSession() {
SessionFactory sessionFactory = ((HibernateEntityManagerFactory) EMF.get()).getSessionFactory();
return sessionFactory.getCurrentSession();
}
}

View File

@ -0,0 +1,84 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*
*/
package com.sismics.util.jpa;
import java.sql.Connection;
import java.sql.SQLException;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.service.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.tool.hbm2ddl.ConnectionHelper;
/**
* A {@link ConnectionHelper} implementation based on a provided
* {@link ConnectionProvider}. Essentially, ensures that the connection
* gets cleaned up, but that the provider itself remains usable since it
* was externally provided to us.
*
* @author Steve Ebersole
*/
class SuppliedConnectionProviderConnectionHelper implements ConnectionHelper {
private ConnectionProvider provider;
private Connection connection;
private boolean toggleAutoCommit;
public SuppliedConnectionProviderConnectionHelper(ConnectionProvider provider) {
this.provider = provider;
}
@Override
public void prepare(boolean needsAutoCommit) throws SQLException {
connection = provider.getConnection();
toggleAutoCommit = needsAutoCommit && !connection.getAutoCommit();
if ( toggleAutoCommit ) {
try {
connection.commit();
}
catch( Throwable ignore ) {
// might happen with a managed connection
}
connection.setAutoCommit( true );
}
}
@Override
public Connection getConnection() throws SQLException {
return connection;
}
@Override
public void release() throws SQLException {
// we only release the connection
if ( connection != null ) {
new SqlExceptionHelper().logAndClearWarnings( connection );
if ( toggleAutoCommit ) {
connection.setAutoCommit( false );
}
provider.closeConnection( connection );
connection = null;
}
}
}

View File

@ -0,0 +1,80 @@
package com.sismics.util.log4j;
import org.apache.commons.lang.StringUtils;
/**
* Log search criteria.
*
* @author jtremeaux
*/
public class LogCriteria {
/**
* Logging level (DEBUG, WARN)...
*/
private String level;
/**
* Logger name / tag.
*/
private String tag;
/**
* Message logged.
*/
private String message;
/**
* Getter of level.
*
* @return level
*/
public String getLevel() {
return level;
}
/**
* Setter of level.
*
* @param level level
*/
public void setLevel(String level) {
this.level = StringUtils.lowerCase(level);
}
/**
* Getter of tag.
*
* @return tag
*/
public String getTag() {
return tag;
}
/**
* Setter of tag.
*
* @param tag tag
*/
public void setTag(String tag) {
this.tag = StringUtils.lowerCase(tag);
}
/**
* Getter of message.
*
* @return message
*/
public String getMessage() {
return message;
}
/**
* Setter of message.
*
* @param message message
*/
public void setMessage(String message) {
this.message = StringUtils.lowerCase(message);
}
}

View File

@ -0,0 +1,79 @@
package com.sismics.util.log4j;
/**
* Log entry.
*
* @author jtremeaux
*/
public class LogEntry {
/**
* Time stamp.
*/
private long timestamp;
/**
* Logging level (DEBUG, WARN)...
*/
private String level;
/**
* Logger name / tag.
*/
private String tag;
/**
* Message logged.
*/
private String message;
/**
* Constructor of LogEntry.
*
* @param timestamp Timestamp
* @param level Logging level (DEBUG, WARN)...
* @param tag Logger name / tag
* @param message Message logged
*/
public LogEntry(long timestamp, String level, String tag, String message) {
this.timestamp = timestamp;
this.level = level;
this.tag = tag;
this.message = message;
}
/**
* Getter of timestamp.
*
* @return timestamp
*/
public long getTimestamp() {
return timestamp;
}
/**
* Getter of level.
*
* @return level
*/
public String getLevel() {
return level;
}
/**
* Getter of tag.
*
* @return tag
*/
public String getTag() {
return tag;
}
/**
* Getter of message.
*
* @return message
*/
public String getMessage() {
return message;
}
}

View File

@ -0,0 +1,129 @@
package com.sismics.util.log4j;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;
import com.google.common.collect.Lists;
import com.sismics.docs.core.util.jpa.PaginatedList;
/**
* Memory appender for Log4J.
*
* @author jtremeaux
*/
public class MemoryAppender extends AppenderSkeleton {
/**
* Maximum size of the queue.
*/
private int size;
/**
* Queue of log entries.
*/
private final Queue<LogEntry> logQueue = new ConcurrentLinkedQueue<LogEntry>();
@Override
public boolean requiresLayout() {
return false;
}
@Override
public synchronized void close() {
if (closed) {
return;
}
closed = true;
}
@Override
public synchronized void append(LoggingEvent event) {
// TODO Don't use size()
while (logQueue.size() > size) {
logQueue.remove();
}
if (closed) {
LogLog.warn("This appender is already closed, cannot append event.");
return;
}
String loggerName = getLoggerName(event);
LogEntry logEntry = new LogEntry(System.currentTimeMillis(), event.getLevel().toString(), loggerName, event.getMessage().toString());
logQueue.add(logEntry);
}
/**
* Extracts the class name of the logger, without the package name.
*
* @param event Event
* @return Class name
*/
private String getLoggerName(LoggingEvent event) {
int index = event.getLoggerName().lastIndexOf('.');
return (index > -1) ?
event.getLoggerName().substring(index + 1) :
event.getLoggerName();
}
/**
* Getter of logList.
*
* @return logList
*/
public Queue<LogEntry> getLogList() {
return logQueue;
}
/**
* Setter of size.
*
* @param size size
*/
public void setSize(int size) {
this.size = size;
}
/**
* Find some logs.
*
* @param criteria Search criteria
* @param list Paginated list (modified by side effect)
*/
public void find(LogCriteria criteria, PaginatedList<LogEntry> list) {
List<LogEntry> logEntryList = new LinkedList<LogEntry>();
final String level = criteria.getLevel();
final String tag = criteria.getTag();
final String message = criteria.getMessage();
int resultCount = 0;
for (Iterator<LogEntry> it = logQueue.iterator(); it.hasNext();) {
LogEntry logEntry = it.next();
if ((level == null || logEntry.getLevel().toLowerCase().equals(level)) &&
(tag == null || logEntry.getTag().toLowerCase().equals(tag)) &&
(message == null || logEntry.getMessage().toLowerCase().contains(message))) {
logEntryList.add(logEntry);
resultCount++;
}
}
int fromIndex = logEntryList.size() - list.getOffset() - list.getLimit();
if (fromIndex < 0) {
fromIndex = 0;
}
int toIndex = logEntryList.size() - list.getOffset();
if (toIndex > logEntryList.size()) {
toIndex = logEntryList.size();
}
List<LogEntry> logEntrySubList = Lists.reverse(logEntryList.subList(fromIndex, toIndex));
list.setResultCount(resultCount);
list.setResultList(logEntrySubList);
}
}

View File

@ -0,0 +1,19 @@
package com.sismics.util.mime;
/**
* A collection of MIME types.
*
* @author jtremeaux
*/
public class MimeType {
public static final String IMAGE_X_ICON = "image/x-icon";
public static final String IMAGE_PNG = "image/png";
public static final String IMAGE_JPEG = "image/jpeg";
public static final String IMAGE_GIF = "image/gif";
public static final String APPLICATION_ZIP = "application/zip";
}

View File

@ -0,0 +1,45 @@
package com.sismics.util.mime;
import java.io.InputStream;
import java.io.PushbackInputStream;
/**
* Utility to check MIME types.
*
* @author jtremeaux
*/
public class MimeTypeUtil {
/**
* Try to guess the MIME type of a file by its magic number (header).
*
* @param file File to inspect
* @return MIME type
* @throws Exception
*/
public static String guessMimeType(InputStream is) throws Exception {
byte[] headerBytes = new byte[64];
PushbackInputStream pb = new PushbackInputStream(is, headerBytes.length);
int readCount = pb.read(headerBytes);
pb.unread(headerBytes);
if (readCount <= 0) {
throw new Exception("Cannot read input file");
}
String header = new String(headerBytes, "US-ASCII");
if (header.startsWith("PK")) {
return MimeType.APPLICATION_ZIP;
} else if (header.startsWith("GIF87a") || header.startsWith("GIF89a")) {
return MimeType.IMAGE_GIF;
} else if (headerBytes[0] == ((byte) 0xff) && headerBytes[1] == ((byte) 0xd8)) {
return MimeType.IMAGE_JPEG;
} else if (headerBytes[0] == ((byte) 0x89) && headerBytes[1] == ((byte) 0x50) && headerBytes[2] == ((byte) 0x4e) && headerBytes[3] == ((byte) 0x47) &&
headerBytes[4] == ((byte) 0x0d) && headerBytes[5] == ((byte) 0x0a) && headerBytes[6] == ((byte) 0x1a) && headerBytes[7] == ((byte) 0x0a)) {
return MimeType.IMAGE_PNG;
} else if (headerBytes[0] == ((byte) 0x00) && headerBytes[1] == ((byte) 0x00) && headerBytes[2] == ((byte) 0x01) && headerBytes[3] == ((byte) 0x00)) {
return MimeType.IMAGE_X_ICON;
}
return null;
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="transactions-optional" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>com.sismics.docs.core.model.jpa.AuthenticationToken</class>
<class>com.sismics.docs.core.model.jpa.BaseFunction</class>
<class>com.sismics.docs.core.model.jpa.Config</class>
<class>com.sismics.docs.core.model.jpa.Locale</class>
<class>com.sismics.docs.core.model.jpa.User</class>
<class>com.sismics.docs.core.model.jpa.RoleBaseFunction</class>
<class>com.sismics.docs.core.model.jpa.Document</class>
</persistence-unit>
</persistence>

View File

@ -0,0 +1 @@
db.version=0

View File

@ -0,0 +1,24 @@
create memory table T_AUTHENTICATION_TOKEN ( AUT_ID_C varchar(36) not null, AUT_IDUSER_C varchar(36) not null, AUT_LONGLASTED_B bit not null, AUT_CREATIONDATE_D datetime not null, AUT_LASTCONNECTIONDATE_D datetime, primary key (AUT_ID_C) );
create memory table T_BASE_FUNCTION ( BAF_ID_C varchar(20) not null, primary key (BAF_ID_C) );
create cached table T_FILE ( FIL_ID_C varchar(36) not null, FIL_IDDOC_C varchar(36) not null, FIL_MIMETYPE_C varchar(100) not null, FIL_CREATEDATE_D datetime, FIL_DELETEDATE_D datetime, primary key (FIL_ID_C) );
create memory table T_CONFIG ( CFG_ID_C varchar(50) not null, CFG_VALUE_C varchar(250) not null, primary key (CFG_ID_C) );
create memory table T_LOCALE ( LOC_ID_C varchar(10) not null, primary key (LOC_ID_C) );
create cached table T_DOCUMENT ( DOC_ID_C varchar(36) not null, DOC_IDUSER_C varchar(36) not null, DOC_TITLE_C varchar(100) not null, DOC_DESCRIPTION_C varchar(4000), DOC_CREATEDATE_D datetime, DOC_DELETEDATE_D datetime, primary key (DOC_ID_C) );
create memory table T_USER ( USE_ID_C varchar(36) not null, USE_IDLOCALE_C varchar(10) not null, USE_IDROLE_C varchar(36) not null, USE_USERNAME_C varchar(50) not null, USE_PASSWORD_C varchar(60) not null, USE_EMAIL_C varchar(100) not null, USE_THEME_C varchar(100) not null, USE_FIRSTCONNECTION_B bit not null, USE_CREATEDATE_D datetime not null, USE_DELETEDATE_D datetime, primary key (USE_ID_C) );
create memory table T_ROLE ( ROL_ID_C varchar(36) not null, ROL_NAME_C varchar(36) not null, ROL_CREATEDATE_D datetime not null, ROL_DELETEDATE_D datetime, primary key (ROL_ID_C) );
create memory table T_ROLE_BASE_FUNCTION ( RBF_ID_C varchar(36) not null, RBF_IDROLE_C varchar(36) not null, RBF_IDBASEFUNCTION_C varchar(20) not null, RBF_CREATEDATE_D datetime not null, RBF_DELETEDATE_D datetime, primary key (RBF_ID_C) );
alter table T_AUTHENTICATION_TOKEN add constraint FK_AUT_IDUSER_C foreign key (AUT_IDUSER_C) references T_USER (USE_ID_C) on delete restrict on update restrict;
alter table T_DOCUMENT add constraint FK_DOC_IDUSER_C foreign key (DOC_IDUSER_C) references T_USER (USE_ID_C) on delete restrict on update restrict;
alter table T_FILE add constraint FK_FIL_IDDOC_C foreign key (FIL_IDDOC_C) references T_DOCUMENT (DOC_ID_C) on delete restrict on update restrict;
alter table T_USER add constraint FK_USE_IDLOCALE_C foreign key (USE_IDLOCALE_C) references T_LOCALE (LOC_ID_C) on delete restrict on update restrict;
alter table T_USER add constraint FK_USE_IDROLE_C foreign key (USE_IDROLE_C) references T_ROLE (ROL_ID_C) on delete restrict on update restrict;
alter table T_ROLE_BASE_FUNCTION add constraint FK_RBF_IDROLE_C foreign key (RBF_IDROLE_C) references T_ROLE (ROL_ID_C) on delete restrict on update restrict;
alter table T_ROLE_BASE_FUNCTION add constraint FK_RBF_IDBASEFUNCTION_C foreign key (RBF_IDBASEFUNCTION_C) references T_BASE_FUNCTION (BAF_ID_C) on delete restrict on update restrict;
insert into T_CONFIG(CFG_ID_C, CFG_VALUE_C) values('DB_VERSION', '0');
insert into T_CONFIG(CFG_ID_C, CFG_VALUE_C) values('LUCENE_DIRECTORY_STORAGE', 'FILE');
insert into T_BASE_FUNCTION(BAF_ID_C) values('ADMIN');
insert into T_LOCALE(LOC_ID_C) values('en');
insert into T_LOCALE(LOC_ID_C) values('fr');
insert into T_ROLE(ROL_ID_C, ROL_NAME_C, ROL_CREATEDATE_D) values('admin', 'Admin', NOW());
insert into T_ROLE_BASE_FUNCTION(RBF_ID_C, RBF_IDROLE_C, RBF_IDBASEFUNCTION_C, RBF_CREATEDATE_D) values('admin_ADMIN', 'admin', 'ADMIN', NOW());
insert into T_USER(USE_ID_C, USE_IDLOCALE_C, USE_IDROLE_C, USE_USERNAME_C, USE_PASSWORD_C, USE_EMAIL_C, USE_THEME_C, USE_FIRSTCONNECTION_B, USE_CREATEDATE_D) values('admin', 'en', 'admin', 'admin', '$2a$05$6Ny3TjrW3aVAL1or2SlcR.fhuDgPKp5jp.P9fBXwVNePgeLqb4i3C', 'admin@localhost', 'default.less', true, NOW());

View File

@ -0,0 +1,31 @@
package com.sismics.docs;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import org.junit.After;
import org.junit.Before;
import com.sismics.util.context.ThreadLocalContext;
import com.sismics.util.jpa.EMF;
/**
* Base class of tests with a transactional context.
*
* @author jtremeaux
*/
public abstract class BaseTransactionalTest {
@Before
public void setUp() throws Exception {
// Initialize the entity manager
EntityManager em = EMF.get().createEntityManager();
ThreadLocalContext context = ThreadLocalContext.get();
context.setEntityManager(em);
EntityTransaction tx = em.getTransaction();
tx.begin();
}
@After
public void tearDown() throws Exception {
}
}

View File

@ -0,0 +1,36 @@
package com.sismics.docs.core.dao.jpa;
import junit.framework.Assert;
import org.junit.Test;
import com.sismics.docs.BaseTransactionalTest;
import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.docs.core.util.TransactionUtil;
/**
* Tests the persistance layer.
*
* @author jtremeaux
*/
public class TestJpa extends BaseTransactionalTest {
@Test
public void testJpa() throws Exception {
// Create a user
UserDao userDao = new UserDao();
User user = new User();
user.setUsername("username");
user.setEmail("toto@docs.com");
user.setLocaleId("fr");
user.setRoleId("admin");
String id = userDao.create(user);
TransactionUtil.commit();
// Search a user by his ID
user = userDao.getById(id);
Assert.assertNotNull(user);
Assert.assertEquals("toto@docs.com", user.getEmail());
}
}

View File

@ -0,0 +1,18 @@
package com.sismics.util;
import junit.framework.Assert;
import org.junit.Test;
/**
* Test of the date utilities.
*
* @author jtremeaux
*/
public class TestDateUtil {
@Test
public void guessTimezoneCodeTest() throws Exception {
Assert.assertEquals("Thu, 04 APR 2013 20:37:27 +10", DateUtil.guessTimezoneOffset("Thu, 04 APR 2013 20:37:27 AEST"));
}
}

View File

@ -0,0 +1,30 @@
package com.sismics.util;
import java.util.List;
import junit.framework.Assert;
import org.junit.Test;
/**
* Test of the resource utils.
*
* @author jtremeaux
*/
public class TestResourceUtil {
@Test
public void listFilesTest() throws Exception {
List<String> fileList = ResourceUtil.list(Test.class, "/junit/framework");
Assert.assertTrue(fileList.contains("Test.class"));
fileList = ResourceUtil.list(Test.class, "/junit/framework/");
Assert.assertTrue(fileList.contains("Test.class"));
fileList = ResourceUtil.list(Test.class, "junit/framework/");
Assert.assertTrue(fileList.contains("Test.class"));
fileList = ResourceUtil.list(Test.class, "junit/framework/");
Assert.assertTrue(fileList.contains("Test.class"));
}
}

View File

@ -0,0 +1,10 @@
hibernate.connection.driver_class=org.hsqldb.jdbcDriver
hibernate.connection.url=jdbc:hsqldb:mem:dùs
hibernate.connection.username=sa
hibernate.connection.password=
hibernate.hbm2ddl.auto=none
hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.show_sql=true
hibernate.format_sql=false
hibernate.max_fetch_depth=5
hibernate.cache.use_second_level_cache=false

View File

@ -0,0 +1,8 @@
log4j.rootCategory=DEBUG, CONSOLE, MEMORY
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{DATE} %p %l %m %n
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
log4j.appender.MEMORY.size=1000
log4j.logger.com.sismics=DEBUG

453
docs-parent/pom.xml Normal file
View File

@ -0,0 +1,453 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<name>Docs Parent</name>
<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Dependencies version (external) -->
<org.apache.commons.commons-compress.version>1.5</org.apache.commons.commons-compress.version>
<commons-lang.commons-lang.version>2.6</commons-lang.commons-lang.version>
<commons-io.commons-io.version>2.1</commons-io.commons-io.version>
<com.google.guava.guava.version>14.0</com.google.guava.guava.version>
<log4j.log4j.version>1.2.16</log4j.log4j.version>
<org.slf4j.version>1.6.4</org.slf4j.version>
<org.slf4j.jcl-over-slf4j.version>1.6.6</org.slf4j.jcl-over-slf4j.version>
<junit.junit.version>4.7</junit.junit.version>
<org.hsqldb.hsqldb.version>2.2.9</org.hsqldb.hsqldb.version>
<com.sun.jersey.version>1.17</com.sun.jersey.version>
<org.mindrot.jbcrypt>0.3m</org.mindrot.jbcrypt>
<org.subethamail.subethasmtp.version>3.1.6</org.subethamail.subethasmtp.version>
<org.subethamail.subethasmtp-wiser.version>1.2</org.subethamail.subethasmtp-wiser.version>
<org.codehaus.jettison.jettison.version>1.1</org.codehaus.jettison.jettison.version>
<org.ccil.cowan.tagsoup.tagsoup.version>1.2.1</org.ccil.cowan.tagsoup.tagsoup.version>
<com.googlecode.owasp-java-html-sanitizer.owasp-java-html-sanitizer.version>r156</com.googlecode.owasp-java-html-sanitizer.owasp-java-html-sanitizer.version>
<org.apache.lucene.version>4.2.0</org.apache.lucene.version>
<jgoodies.forms.version>1.0.5</jgoodies.forms.version>
<org.bitlet.weupnp.version>0.1.2</org.bitlet.weupnp.version>
<com.sun.grizzly.version>1.9.18-m</com.sun.grizzly.version>
<org.hibernate.hibernate.version>4.1.0.Final</org.hibernate.hibernate.version>
<commons-dbcp.version>1.4</commons-dbcp.version>
<joda-time.joda-time.version>2.2</joda-time.joda-time.version>
<org.codehaus.jackson.jackson.version>1.7.1</org.codehaus.jackson.jackson.version>
<org.eclipse.jetty.jetty-server.version>8.1.10.v20130312</org.eclipse.jetty.jetty-server.version>
<org.eclipse.jetty.jetty-webapp.version>8.1.10.v20130312</org.eclipse.jetty.jetty-webapp.version>
<org.eclipse.jetty.jetty-servlet.version>8.1.10.v20130312</org.eclipse.jetty.jetty-servlet.version>
<org.mortbay.jetty.servlet-api.version>3.0.20100224</org.mortbay.jetty.servlet-api.version>
<!-- Plugins version -->
<org.apache.maven.plugins.maven-antrun-plugin.version>1.7</org.apache.maven.plugins.maven-antrun-plugin.version>
<org.apache.maven.plugins.maven-dependency-plugin.version>2.7</org.apache.maven.plugins.maven-dependency-plugin.version>
<org.apache.maven.plugins.maven-eclipse-plugin.version>2.8</org.apache.maven.plugins.maven-eclipse-plugin.version>
<org.apache.maven.plugins.maven-jar-plugin.version>2.4</org.apache.maven.plugins.maven-jar-plugin.version>
<org.apache.maven.plugins.maven-release-plugin.version>2.4.1</org.apache.maven.plugins.maven-release-plugin.version>
<org.apache.maven.plugins.maven-resources-plugin.version>2.6</org.apache.maven.plugins.maven-resources-plugin.version>
<org.apache.maven.plugins.maven-war-plugin.version>2.2</org.apache.maven.plugins.maven-war-plugin.version>
<org.codehaus.cargo.cargo-maven2-plugin.version>1.2.0</org.codehaus.cargo.cargo-maven2-plugin.version>
<org.codehaus.mojo.rpm-maven-plugin.version>2.1-alpha-2</org.codehaus.mojo.rpm-maven-plugin.version>
<org.codehaus.mojo.nsis-maven-plugin.version>1.0-SNAPSHOT</org.codehaus.mojo.nsis-maven-plugin.version>
<org.codehaus.mojo.osxappbundle-maven-plugin.version>1.0-alpha-2</org.codehaus.mojo.osxappbundle-maven-plugin.version>
<org.mortbay.jetty.jetty-maven-plugin.version>8.1.2.v20120308</org.mortbay.jetty.jetty-maven-plugin.version>
<org.vafer.jdeb.version>1.0.1</org.vafer.jdeb.version>
<com.samaxes.maven.minify-maven-plugin.version>1.7</com.samaxes.maven.minify-maven-plugin.version>
</properties>
<scm>
<connection>scm:git:https://github.com/sismics/docs.git</connection>
<developerConnection>scm:git:https://github.com/docs/docs.git</developerConnection>
<url>scm:git:https://github.com/sismics/docs.git</url>
<tag>HEAD</tag>
</scm>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>${org.apache.maven.plugins.maven-antrun-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>${org.apache.maven.plugins.maven-dependency-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>${org.apache.maven.plugins.maven-eclipse-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${org.apache.maven.plugins.maven-jar-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>${org.apache.maven.plugins.maven-release-plugin.version}</version>
<configuration>
<tagNameFormat>@{project.version}</tagNameFormat>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${org.apache.maven.plugins.maven-resources-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${org.apache.maven.plugins.maven-war-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>${org.codehaus.cargo.cargo-maven2-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${org.mortbay.jetty.jetty-maven-plugin.version}</version>
</plugin>
<plugin>
<groupId>com.samaxes.maven</groupId>
<artifactId>minify-maven-plugin</artifactId>
<version>${com.samaxes.maven.minify-maven-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.vafer</groupId>
<artifactId>jdeb</artifactId>
<version>${org.vafer.jdeb.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>rpm-maven-plugin</artifactId>
<version>${org.codehaus.mojo.rpm-maven-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>nsis-maven-plugin</artifactId>
<version>${org.codehaus.mojo.nsis-maven-plugin.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>osxappbundle-maven-plugin</artifactId>
<version>${org.codehaus.mojo.osxappbundle-maven-plugin.version}</version>
</plugin>
</plugins>
</build>
<modules>
<module>../docs-core</module>
<module>../docs-web-common</module>
<module>../docs-web</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-web-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-web-common</artifactId>
<type>test-jar</type>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-web</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${org.eclipse.jetty.jetty-server.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
<version>${org.eclipse.jetty.jetty-webapp.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>${org.eclipse.jetty.jetty-servlet.version}</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>servlet-api</artifactId>
<version>${org.mortbay.jetty.servlet-api.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>${org.apache.commons.commons-compress.version}</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons-lang.commons-lang.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.commons-io.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${com.google.guava.guava.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>${org.codehaus.jackson.jackson.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>${org.codehaus.jackson.jackson.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j.jcl-over-slf4j.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.junit.version}</version>
</dependency>
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>${org.mindrot.jbcrypt}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>${com.sun.jersey.version}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-bundle</artifactId>
<version>${com.sun.jersey.version}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>${com.sun.jersey.version}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
<version>${com.sun.jersey.version}</version>
</dependency>
<dependency>
<groupId>com.sun.grizzly</groupId>
<artifactId>grizzly-servlet-webserver</artifactId>
<version>${com.sun.grizzly.version}</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-grizzly2</artifactId>
<version>${com.sun.jersey.version}</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>${org.hsqldb.hsqldb.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${org.hibernate.hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${org.hibernate.hibernate.version}</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>${commons-dbcp.version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda-time.joda-time.version}</version>
</dependency>
<dependency>
<groupId>org.subethamail</groupId>
<artifactId>subethasmtp-wiser</artifactId>
<version>${org.subethamail.subethasmtp-wiser.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>${org.codehaus.jettison.jettison.version}</version>
</dependency>
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client</artifactId>
<version>${com.google.oauth-client.google-oauth-client.version}</version>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-oauth2</artifactId>
<version>${com.google.apis.google-api-services-oauth2.version}</version>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-jackson2</artifactId>
<version>${com.google.http-client.google-http-client-jackson2.version}</version>
</dependency>
<dependency>
<groupId>org.ccil.cowan.tagsoup</groupId>
<artifactId>tagsoup</artifactId>
<version>${org.ccil.cowan.tagsoup.tagsoup.version}</version>
</dependency>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>${com.googlecode.owasp-java-html-sanitizer.owasp-java-html-sanitizer.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>${org.apache.lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>${org.apache.lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>${org.apache.lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>${org.apache.lucene.version}</version>
</dependency>
<dependency>
<groupId>jgoodies</groupId>
<artifactId>forms</artifactId>
<version>${jgoodies.forms.version}</version>
</dependency>
<dependency>
<groupId>org.bitlet</groupId>
<artifactId>weupnp</artifactId>
<version>${org.bitlet.weupnp.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<pluginRepositories>
<!-- Codehause Snapshots (NSIS) -->
<pluginRepository>
<id>Codehaus Snapshots</id>
<url>http://nexus.codehaus.org/snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>true</enabled> <!-- Workaround for MNG-2974 -->
</releases>
</pluginRepository>
</pluginRepositories>
</project>

130
docs-web-common/pom.xml Normal file
View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../docs-parent</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>docs-web-common</artifactId>
<packaging>jar</packaging>
<name>Docs Web Commons</name>
<dependencies>
<!-- Dependencies to Docs -->
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-core</artifactId>
</dependency>
<!-- Dependencies to Jersey -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-bundle</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
</dependency>
<!-- Other external dependencies -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.grizzly</groupId>
<artifactId>grizzly-servlet-webserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-grizzly2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.subethamail</groupId>
<artifactId>subethasmtp-wiser</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Install test jar -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,54 @@
package com.sismics.rest.exception;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Jersey exception encapsulating an error from the client (BAD_REQUEST).
*
* @author jtremeaux
*/
public class ClientException extends WebApplicationException {
/**
* Serial UID.
*/
private static final long serialVersionUID = 1L;
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(ClientException.class);
/**
* Constructor of ClientException.
*
* @param type Error type (e.g. AlreadyExistingEmail, ValidationError)
* @param message Human readable error message
* @param e Readable error message
* @throws JSONException
*/
public ClientException(String type, String message, Exception e) throws JSONException {
this(type, message);
log.error(type + ": " + message, e);
}
/**
* Constructor of ClientException.
*
* @param type Error type (e.g. AlreadyExistingEmail, ValidationError)
* @param message Human readable error message
* @throws JSONException
*/
public ClientException(String type, String message) throws JSONException {
super(Response.status(Status.BAD_REQUEST).entity(new JSONObject()
.put("type", type)
.put("message", message)).build());
}
}

View File

@ -0,0 +1,31 @@
package com.sismics.rest.exception;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
/**
* Unauthorized access to the resource exception.
*
* @author jtremeaux
*/
public class ForbiddenClientException extends WebApplicationException {
/**
* Serial UID.
*/
private static final long serialVersionUID = 1L;
/**
* Constructor of ForbiddenClientException.
*
* @throws JSONException
*/
public ForbiddenClientException() throws JSONException {
super(Response.status(Status.FORBIDDEN).entity(new JSONObject()
.put("type", "ForbiddenError")
.put("message", "You don't have access to this resource")).build());
}
}

View File

@ -0,0 +1,53 @@
package com.sismics.rest.exception;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
/**
* Jersey exception encapsulating an error from the client (INTERNAL_SERVER_ERROR).
*
* @author jtremeaux
*/
public class ServerException extends WebApplicationException {
/**
* Serial UID.
*/
private static final long serialVersionUID = 1L;
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(ServerException.class);
/**
* Constructor of ClientException.
*
* @param type Error type (e.g. DatabaseError)
* @param message Human readable error message
* @param e Inner exception
* @throws JSONException
*/
public ServerException(String type, String message, Exception e) throws JSONException {
this(type, message);
log.error(type + ": " + message, e);
}
/**
* Constructor of ClientException.
*
* @param type Error type (e.g. DatabaseError)
* @param message Human readable error message
* @throws JSONException
*/
public ServerException(String type, String message) throws JSONException {
super(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new JSONObject()
.put("type", type)
.put("message", message)).build());
}
}

View File

@ -0,0 +1,45 @@
package com.sismics.rest.resource;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Generic exception mapper that transforms all unknown exception into ServerError.
*
* @author jtremeaux
*/
@Provider
public class GenericExceptionMapper implements ExceptionMapper<Exception> {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(GenericExceptionMapper.class);
@Override
public Response toResponse(Exception e) {
if (e instanceof WebApplicationException) {
return ((WebApplicationException) e).getResponse();
}
log.error("Unknown error", e);
JSONObject entity = new JSONObject();
try {
entity.put("type", "UnknownError");
entity.put("message", "Unknown server error");
} catch (JSONException e2) {
log.error("Error building response", e2);
}
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(entity)
.build();
}
}

View File

@ -0,0 +1,40 @@
package com.sismics.rest.util;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
/**
* JSON utilities.
*
* @author jtremeaux
*/
public class JsonUtil {
/**
* Fix of {@see JsonObject.append()}, which seems to create nested arrays.
*
* @param o JSON Object
* @param key Key containing the array of null
* @param value Value to append
* @return Updated object
* @throws JSONException
*/
public static JSONObject append(JSONObject o, String key, JSONObject value) throws JSONException {
Object prevValue = o.opt(key);
if (prevValue == null) {
o.put(key, new JSONArray().put(value));
} else if (!(prevValue instanceof JSONArray)){
throw new JSONException("JSONObject[" + key + "] is not a JSONArray.");
} else {
JSONArray newArray = new JSONArray();
JSONArray oldArray = ((JSONArray) prevValue);
for (int i = 0; i < oldArray.length(); i++) {
newArray.put(oldArray.get(i));
}
newArray.put(value);
o.put(key, newArray);
}
return o;
}
}

View File

@ -0,0 +1,214 @@
package com.sismics.rest.util;
import java.text.MessageFormat;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.codehaus.jettison.json.JSONException;
import org.joda.time.DateTime;
import com.google.common.base.Strings;
import com.sismics.docs.core.dao.file.theme.ThemeDao;
import com.sismics.docs.core.dao.jpa.LocaleDao;
import com.sismics.docs.core.model.jpa.Locale;
import com.sismics.rest.exception.ClientException;
/**
* Utility class to validate parameters.
*
* @author jtremeaux
*/
public class ValidationUtil {
private static Pattern EMAIL_PATTERN = Pattern.compile(".+@.+\\..+");
private static Pattern HTTP_URL_PATTERN = Pattern.compile("https?://.+");
private static Pattern ALPHANUMERIC_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
/**
* Checks that the argument is not null.
*
* @param s Object tu validate
* @param name Name of the parameter
* @throws JSONException
*/
public static void validateRequired(Object s, String name) throws JSONException {
if (s == null) {
throw new ClientException("ValidationError", MessageFormat.format("{0} must be set", name));
}
}
/**
* Validate a string length.
*
* @param s String to validate
* @param name Name of the parameter
* @param lengthMin Minimum length (or null)
* @param lengthMax Maximum length (or null)
* @param nullable True if the string can be empty or null
* @return String without white spaces
* @throws ClientException
*/
public static String validateLength(String s, String name, Integer lengthMin, Integer lengthMax, boolean nullable) throws JSONException {
s = StringUtils.strip(s);
if (nullable && StringUtils.isEmpty(s)) {
return s;
}
if (s == null) {
throw new ClientException("ValidationError", MessageFormat.format("{0} must be set", name));
}
if (lengthMin != null && s.length() < lengthMin) {
throw new ClientException("ValidationError", MessageFormat.format("{0} must be more than {1} characters", name, lengthMin));
}
if (lengthMax != null && s.length() > lengthMax) {
throw new ClientException("ValidationError", MessageFormat.format("{0} must be more than {1} characters", name, lengthMax));
}
return s;
}
/**
* Validate a string length. The string mustn't be empty.
*
* @param s String to validate
* @param name Name of the parameter
* @param lengthMin Minimum length (or null)
* @param lengthMax Maximum length (or null)
* @return String without white spaces
* @throws ClientException
*/
public static String validateLength(String s, String name, Integer lengthMin, Integer lengthMax) throws JSONException {
return validateLength(s, name, lengthMin, lengthMax, false);
}
/**
* Checks if the string is not null and is not only whitespaces.
*
* @param s String to validate
* @param name Name of the parameter
* @return String without white spaces
* @throws JSONException
*/
public static String validateStringNotBlank(String s, String name) throws JSONException {
return validateLength(s, name, 1, null, false);
}
/**
* Checks if the string is an email.
*
* @param s String to validate
* @param name Name of the parameter
* @throws JSONException
*/
public static void validateEmail(String s, String name) throws JSONException {
if (!EMAIL_PATTERN.matcher(s).matches()) {
throw new ClientException("ValidationError", MessageFormat.format("{0} must be an email", name));
}
}
/**
* Validates that the provided string matches an URL with HTTP or HTTPS scheme.
*
* @param s String to validate
* @param name Name of the parameter
* @return Stripped URL
* @throws JSONException
*/
public static String validateHttpUrl(String s, String name) throws JSONException {
s = StringUtils.strip(s);
if (!HTTP_URL_PATTERN.matcher(s).matches()) {
throw new ClientException("ValidationError", MessageFormat.format("{0} must be an HTTP(s) URL", name));
}
return s;
}
/**
* Checks if the string uses only alphanumerical or underscore characters.
*
* @param s String to validate
* @param name Name of the parameter
* @throws JSONException
*/
public static void validateAlphanumeric(String s, String name) throws JSONException {
if (!ALPHANUMERIC_PATTERN.matcher(s).matches()) {
throw new ClientException("ValidationError", MessageFormat.format("{0} must have only alphanumeric or underscore characters", name));
}
}
/**
* Validates and parses a date.
*
* @param s String to validate
* @param name Name of the parameter
* @param nullable True if the string can be empty or null
* @return Parsed date
* @throws JSONException
*/
public static Date validateDate(String s, String name, boolean nullable) throws JSONException {
if (Strings.isNullOrEmpty(s)) {
if (!nullable) {
throw new ClientException("ValidationError", MessageFormat.format("{0} must be set", name));
} else {
return null;
}
}
try {
return new DateTime(Long.parseLong(s)).toDate();
} catch (NumberFormatException e) {
throw new ClientException("ValidationError", MessageFormat.format("{0} must be a date", name));
}
}
/**
* Validates a locale.
*
* @param localeId String to validate
* @param name Name of the parameter
* @return String without white spaces
* @param nullable True if the string can be empty or null
* @throws ClientException
*/
public static String validateLocale(String localeId, String name, boolean nullable) throws JSONException {
localeId = StringUtils.strip(localeId);
if (StringUtils.isEmpty(localeId)) {
if (!nullable) {
throw new ClientException("ValidationError", MessageFormat.format("{0} is required", name));
} else {
return null;
}
}
LocaleDao localeDao = new LocaleDao();
Locale locale = localeDao.getById(localeId);
if (locale == null) {
throw new ClientException("ValidationError", "Locale not found: " + localeId);
}
return localeId;
}
/**
* Validates a theme.
*
* @param themeId ID of the theme to validate
* @param name Name of the parameter
* @return String without white spaces
* @param nullable True if the string can be empty or null
* @throws ClientException
*/
public static String validateTheme(String themeId, String name, boolean nullable) throws JSONException {
themeId = StringUtils.strip(themeId);
if (StringUtils.isEmpty(themeId)) {
if (!nullable) {
throw new ClientException("ValidationError", MessageFormat.format("{0} is required", name));
} else {
return null;
}
}
ThemeDao themeDao = new ThemeDao();
List<String> themeList = themeDao.findAll();
if (!themeList.contains(themeId)) {
throw new ClientException("ValidationError", "Theme not found: " + themeId);
}
return themeId;
}
}

View File

@ -0,0 +1,79 @@
package com.sismics.security;
import java.util.Locale;
import org.joda.time.DateTimeZone;
/**
* Anonymous principal.
*
* @author jtremeaux
*/
public class AnonymousPrincipal implements IPrincipal {
public static final String ANONYMOUS = "anonymous";
/**
* User locale.
*/
private Locale locale;
/**
* User timezone.
*/
private DateTimeZone dateTimeZone;
/**
* Constructor of AnonymousPrincipal.
*/
public AnonymousPrincipal() {
// NOP
}
@Override
public String getId() {
return null;
}
@Override
public String getName() {
return ANONYMOUS;
}
@Override
public boolean isAnonymous() {
return true;
}
@Override
public Locale getLocale() {
return locale;
}
/**
* Setter of locale.
*
* @param locale locale
*/
public void setLocale(Locale locale) {
this.locale = locale;
}
@Override
public DateTimeZone getDateTimeZone() {
return dateTimeZone;
}
@Override
public String getEmail() {
return null;
}
/**
* Setter of dateTimeZone.
*
* @param dateTimeZone dateTimeZone
*/
public void setDateTimeZone(DateTimeZone dateTimeZone) {
this.dateTimeZone = dateTimeZone;
}
}

View File

@ -0,0 +1,48 @@
package com.sismics.security;
import java.security.Principal;
import java.util.Locale;
import org.joda.time.DateTimeZone;
/**
* Interface of principals.
*
* @author jtremeaux
*/
public interface IPrincipal extends Principal {
/**
* Checks if the principal is anonymous.
*
* @return True if the principal is anonymous.
*/
boolean isAnonymous();
/**
* Returns the ID of the connected user, or null if the user is anonymous
*
* @return ID of the connected user
*/
public String getId();
/**
* Returns the locale of the principal.
*
* @return Locale of the principal
*/
public Locale getLocale();
/**
* Returns the timezone of the principal.
*
* @return Timezone of the principal
*/
public DateTimeZone getDateTimeZone();
/**
* Returns the email of the principal.
*
* @return Email of the principal
*/
public String getEmail();
}

View File

@ -0,0 +1,148 @@
package com.sismics.security;
import java.util.Locale;
import java.util.Set;
import org.joda.time.DateTimeZone;
/**
* Authenticated users principal.
*
* @author jtremeaux
*/
public class UserPrincipal implements IPrincipal {
/**
* ID of the user.
*/
private String id;
/**
* Username of the user.
*/
private String name;
/**
* Locale of the principal.
*/
private Locale locale;
/**
* Timezone of the principal.
*/
private DateTimeZone dateTimeZone;
/**
* Email of the principal.
*/
private String email;
/**
* User base functions.
*/
private Set<String> baseFunctionSet;
/**
* Constructor of UserPrincipal.
*
* @param id ID of the user
* @param name Usrename of the user
*/
public UserPrincipal(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean isAnonymous() {
return false;
}
@Override
public String getId() {
return id;
}
/**
* Setter of id.
*
* @param id id
*/
public void setId(String id) {
this.id = id;
}
@Override
public String getName() {
return name;
}
/**
* Setter of name.
*
* @param name name
*/
public void setName(String name) {
this.name = name;
}
@Override
public Locale getLocale() {
return locale;
}
/**
* Setter of locale.
*
* @param locale locale
*/
public void setLocale(Locale locale) {
this.locale = locale;
}
@Override
public DateTimeZone getDateTimeZone() {
return dateTimeZone;
}
/**
* Setter of dateTimeZone.
*
* @param dateTimeZone dateTimeZone
*/
public void setDateTimeZone(DateTimeZone dateTimeZone) {
this.dateTimeZone = dateTimeZone;
}
@Override
public String getEmail() {
return email;
}
/**
* Setter of email.
*
* @param email email
*/
public void setEmail(String email) {
this.email = email;
}
/**
* Getter of baseFunctionSet.
*
* @return baseFunctionSet
*/
public Set<String> getBaseFunctionSet() {
return baseFunctionSet;
}
/**
* Setter of baseFunctionSet.
*
* @param baseFunctionSet baseFunctionSet
*/
public void setBaseFunctionSet(Set<String> baseFunctionSet) {
this.baseFunctionSet = baseFunctionSet;
}
}

View File

@ -0,0 +1,151 @@
package com.sismics.util.filter;
import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Locale;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Level;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.RollingFileAppender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.model.context.AppContext;
import com.sismics.docs.core.util.DirectoryUtil;
import com.sismics.docs.core.util.TransactionUtil;
import com.sismics.util.EnvironmentUtil;
import com.sismics.util.context.ThreadLocalContext;
import com.sismics.util.jpa.EMF;
/**
* Filter used to process a couple things in the request context.
*
* @author jtremeaux
*/
public class RequestContextFilter implements Filter {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(RequestContextFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Force the locale in order to not depend on the execution environment
Locale.setDefault(new Locale(Constants.DEFAULT_LOCALE_ID));
// Injects the webapp root
String webappRoot = filterConfig.getServletContext().getRealPath("/");
EnvironmentUtil.setWebappRoot(webappRoot);
// Initialize the app directory
File baseDataDirectory = null;
try {
baseDataDirectory = DirectoryUtil.getBaseDataDirectory();
} catch (Exception e) {
log.error("Error initializing base data directory", e);
}
if (log.isInfoEnabled()) {
log.info(MessageFormat.format("Using base data directory: {0}", baseDataDirectory.toString()));
}
// Initialize file logger
RollingFileAppender fileAppender = new RollingFileAppender();
fileAppender.setName("FILE");
fileAppender.setFile(DirectoryUtil.getLogDirectory() + File.separator + "docs.log");
fileAppender.setLayout(new PatternLayout("%d{DATE} %p %l %m %n"));
fileAppender.setThreshold(Level.INFO);
fileAppender.setAppend(true);
fileAppender.setMaxFileSize("5MB");
fileAppender.setMaxBackupIndex(5);
fileAppender.activateOptions();
org.apache.log4j.Logger.getRootLogger().addAppender(fileAppender);
// Initialize the application context
TransactionUtil.handle(new Runnable() {
@Override
public void run() {
AppContext.getInstance();
}
});
}
@Override
public void destroy() {
// NOP
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
EntityManager em = null;
try {
em = EMF.get().createEntityManager();
} catch (Exception e) {
throw new ServletException("Cannot create entity manager", e);
}
ThreadLocalContext context = ThreadLocalContext.get();
context.setEntityManager(em);
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
filterChain.doFilter(request, response);
} catch (Exception e) {
ThreadLocalContext.cleanup();
log.error("An exception occured, rolling back current transaction", e);
// If an unprocessed error comes up from the application layers (Jersey...), rollback the transaction (should not happen)
if (em.isOpen()) {
if (em.getTransaction() != null && em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
try {
em.close();
} catch (Exception ce) {
log.error("Error closing entity manager", ce);
}
}
throw new ServletException(e);
}
ThreadLocalContext.cleanup();
// No error processing the request : commit / rollback the current transaction depending on the HTTP code
if (em.isOpen()) {
if (em.getTransaction() != null && em.getTransaction().isActive()) {
HttpServletResponse r = (HttpServletResponse) response;
int statusClass = r.getStatus() / 100;
if (statusClass == 2 || statusClass == 3) {
try {
em.getTransaction().commit();
} catch (Exception e) {
log.error("Error during commit", e);
r.sendError(500);
}
} else {
em.getTransaction().rollback();
}
try {
em.close();
} catch (Exception e) {
log.error("Error closing entity manager", e);
}
}
}
}
}

View File

@ -0,0 +1,180 @@
package com.sismics.util.filter;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.joda.time.DateTimeZone;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sismics.docs.core.constant.Constants;
import com.sismics.docs.core.dao.jpa.AuthenticationTokenDao;
import com.sismics.docs.core.dao.jpa.RoleBaseFunctionDao;
import com.sismics.docs.core.dao.jpa.UserDao;
import com.sismics.docs.core.model.jpa.AuthenticationToken;
import com.sismics.docs.core.model.jpa.User;
import com.sismics.security.AnonymousPrincipal;
import com.sismics.security.UserPrincipal;
import com.sismics.util.LocaleUtil;
/**
* This filter is used to authenticate the user having an active session via an authentication token stored in database.
* The filter extracts the authentication token stored in a cookie.
* If the ocokie exists and the token is valid, the filter injects a UserPrincipal into a request attribute.
* If not, the user is anonymous, and the filter injects a AnonymousPrincipal into the request attribute.
*
* @author jtremeaux
*/
public class TokenBasedSecurityFilter implements Filter {
/**
* Logger.
*/
private static final Logger log = LoggerFactory.getLogger(TokenBasedSecurityFilter.class);
/**
* Name of the cookie used to store the authentication token.
*/
public static final String COOKIE_NAME = "auth_token";
/**
* Name of the attribute containing the principal.
*/
public static final String PRINCIPAL_ATTRIBUTE = "principal";
/**
* Lifetime of the authentication token in seconds, since login.
*/
public static final int TOKEN_LONG_LIFETIME = 3600 * 24 * 365 * 20;
/**
* Lifetime of the authentication token in seconds, since last connection.
*/
public static final int TOKEN_SESSION_LIFETIME = 3600 * 24;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// NOP
}
@Override
public void destroy() {
// NOP
}
@Override
public void doFilter(ServletRequest req, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
// Get the value of the client authentication token
HttpServletRequest request = (HttpServletRequest) req;
String authToken = null;
if (request.getCookies() != null) {
for (Cookie cookie : request.getCookies()) {
if (COOKIE_NAME.equals(cookie.getName())) {
authToken = cookie.getValue();
}
}
}
// Get the corresponding server token
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
AuthenticationToken authenticationToken = null;
if (authToken != null) {
authenticationToken = authenticationTokenDao.get(authToken);
}
if (authenticationToken == null) {
injectAnonymousUser(request);
} else {
// Check if the token is still valid
if (isTokenExpired(authenticationToken)) {
try {
injectAnonymousUser(request);
// Destroy the expired token
authenticationTokenDao.delete(authToken);
} catch (Exception e) {
if (log.isErrorEnabled()) {
log.error(MessageFormat.format("Error deleting authentication token {0} ", authToken), e);
}
}
} else {
// Check if the user is still valid
UserDao userDao = new UserDao();
User user = userDao.getById(authenticationToken.getUserId());
if (user != null && user.getDeleteDate() == null) {
injectAuthenticatedUser(request, user);
// Update the last connection date
authenticationTokenDao.updateLastConnectionDate(authenticationToken.getId());
} else {
injectAnonymousUser(request);
}
}
}
filterChain.doFilter(request, response);
}
/**
* Returns true if the token is expired.
*
* @param authenticationToken Authentication token
* @return Token expired
*/
private boolean isTokenExpired(AuthenticationToken authenticationToken) {
final long now = new Date().getTime();
final long creationDate = authenticationToken.getCreationDate().getTime();
if (authenticationToken.isLongLasted()) {
return now >= creationDate + ((long) TOKEN_LONG_LIFETIME) * 1000L;
} else {
long date = authenticationToken.getLastConnectionDate() != null ?
authenticationToken.getLastConnectionDate().getTime() : creationDate;
return now >= date + ((long) TOKEN_SESSION_LIFETIME) * 1000L;
}
}
/**
* Inject an authenticated user into the request attributes.
*
* @param request HTTP request
* @param user User to inject
*/
private void injectAuthenticatedUser(HttpServletRequest request, User user) {
UserPrincipal userPrincipal = new UserPrincipal(user.getId(), user.getUsername());
// Add locale
Locale locale = LocaleUtil.getLocale(user.getLocaleId());
userPrincipal.setLocale(locale);
// Add base functions
RoleBaseFunctionDao userBaseFuction = new RoleBaseFunctionDao();
Set<String> baseFunctionSet = userBaseFuction.findByRoleId(user.getRoleId());
userPrincipal.setBaseFunctionSet(baseFunctionSet);
request.setAttribute(PRINCIPAL_ATTRIBUTE, userPrincipal);
}
/**
* Inject an anonymous user into the request attributes.
*
* @param request HTTP request
*/
private void injectAnonymousUser(HttpServletRequest request) {
AnonymousPrincipal anonymousPrincipal = new AnonymousPrincipal();
anonymousPrincipal.setLocale(request.getLocale());
anonymousPrincipal.setDateTimeZone(DateTimeZone.forID(Constants.DEFAULT_TIMEZONE_ID));
request.setAttribute(PRINCIPAL_ATTRIBUTE, anonymousPrincipal);
}
}

View File

@ -0,0 +1,115 @@
package com.sismics.docs.rest;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLDecoder;
import java.util.List;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.StaticHttpHandler;
import org.junit.After;
import org.junit.Before;
import org.subethamail.wiser.Wiser;
import org.subethamail.wiser.WiserMessage;
import com.sismics.docs.rest.descriptor.JerseyTestWebAppDescriptorFactory;
import com.sismics.docs.rest.util.ClientUtil;
import com.sun.jersey.test.framework.JerseyTest;
/**
* Base class of integration tests with Jersey.
*
* @author jtremeaux
*/
public abstract class BaseJerseyTest extends JerseyTest {
/**
* Test email server.
*/
protected Wiser wiser;
/**
* Test HTTP server.
*/
HttpServer httpServer;
/**
* Utility class for the REST client.
*/
protected ClientUtil clientUtil;
/**
* Constructor of BaseJerseyTest.
*/
public BaseJerseyTest() {
super(JerseyTestWebAppDescriptorFactory.build());
this.clientUtil = new ClientUtil(resource());
}
@Override
@Before
public void setUp() throws Exception {
super.setUp();
wiser = new Wiser();
wiser.setPort(2500);
wiser.start();
String httpRoot = URLDecoder.decode(new File(getClass().getResource("/").getFile()).getAbsolutePath(), "utf-8");
httpServer = HttpServer.createSimpleServer(httpRoot, "localhost", 9997);
// Disable file cache to fix https://java.net/jira/browse/GRIZZLY-1350
((StaticHttpHandler) httpServer.getServerConfiguration().getHttpHandlers().keySet().iterator().next()).setFileCacheEnabled(false);
httpServer.start();
}
@Override
@After
public void tearDown() throws Exception {
super.tearDown();
wiser.stop();
httpServer.stop();
}
/**
* Extracts an email from the queue and consumes the email.
*
* @return Text of the email
* @throws MessagingException
* @throws IOException
*/
protected String popEmail() throws MessagingException, IOException {
List<WiserMessage> wiserMessageList = wiser.getMessages();
if (wiserMessageList.isEmpty()) {
return null;
}
WiserMessage wiserMessage = wiserMessageList.get(wiserMessageList.size() - 1);
wiserMessageList.remove(wiserMessageList.size() - 1);
MimeMessage message = wiserMessage.getMimeMessage();
ByteArrayOutputStream os = new ByteArrayOutputStream();
message.writeTo(os);
String body = os.toString();
return body;
}
/**
* Encodes a string to "quoted-printable" characters to compare with the contents of an email.
*
* @param input String to encode
* @return Encoded string
* @throws MessagingException
* @throws IOException
*/
protected String encodeQuotedPrintable(String input) throws MessagingException, IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream os = MimeUtility.encode(baos, "quoted-printable");
os.write(input.getBytes());
os.close();
return baos.toString();
}
}

View File

@ -0,0 +1,35 @@
package com.sismics.docs.rest.descriptor;
import java.io.File;
import com.sismics.util.filter.RequestContextFilter;
import com.sismics.util.filter.TokenBasedSecurityFilter;
import com.sun.jersey.test.framework.WebAppDescriptor;
/**
* Jersey tests Webapp descriptor.
*
* @author jtremeaux
*/
public class JerseyTestWebAppDescriptorFactory {
private static String basePath = new File("src/main/webapp").getAbsolutePath();
/**
* Constructs a new descriptor.
*
* @return Descriptor
*/
public static WebAppDescriptor build() {
// Target the base path to the Webapp resources
System.setProperty("user.dir", basePath);
System.setProperty("test", "true");
return new WebAppDescriptor.Builder("com.sismics.docs.rest.resource")
.contextPath("docs")
.addFilter(RequestContextFilter.class, "requestContextFilter")
.addFilter(TokenBasedSecurityFilter.class, "tokenBasedSecurityFilter")
.initParam("com.sun.jersey.spi.container.ContainerRequestFilters", "com.sun.jersey.api.container.filter.LoggingFilter")
.initParam("com.sun.jersey.spi.container.ContainerResponseFilters", "com.sun.jersey.api.container.filter.LoggingFilter")
.build();
}
}

View File

@ -0,0 +1,41 @@
package com.sismics.docs.rest.filter;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.core.Cookie;
import com.sismics.util.filter.TokenBasedSecurityFilter;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.filter.ClientFilter;
/**
* Filter to add the authentication token into a cookie.
*
* @author jtremeaux
*/
public class CookieAuthenticationFilter extends ClientFilter {
private String authToken;
public CookieAuthenticationFilter(String authToken) {
this.authToken = authToken;
}
@Override
public ClientResponse handle(ClientRequest request) throws ClientHandlerException {
Cookie cookie = new Cookie(TokenBasedSecurityFilter.COOKIE_NAME, authToken);
List<Object> cookieList = new ArrayList<Object>();
cookieList.add(cookie);
if (authToken != null) {
request.getHeaders().put("Cookie", cookieList);
}
ClientResponse response = getNext().handle(request);
if (response.getCookies() != null) {
cookieList.addAll(response.getCookies());
}
return response;
}
}

View File

@ -0,0 +1,113 @@
package com.sismics.docs.rest.util;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import junit.framework.Assert;
import com.sismics.docs.rest.filter.CookieAuthenticationFilter;
import com.sismics.util.filter.TokenBasedSecurityFilter;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.core.util.MultivaluedMapImpl;
/**
* REST client utilities.
*
* @author jtremeaux
*/
public class ClientUtil {
private WebResource resource;
/**
* Constructor of ClientUtil.
*
* @param webResource Resource corresponding to the base URI of REST resources.
*/
public ClientUtil(WebResource resource) {
this.resource = resource;
}
/**
* Creates a user.
*
* @param username Username
*/
public void createUser(String username) {
// Login admin to create the user
String adminAuthenticationToken = login("admin", "admin", false);
// Create the user
WebResource userResource = resource.path("/user");
userResource.addFilter(new CookieAuthenticationFilter(adminAuthenticationToken));
MultivaluedMap<String, String> postParams = new MultivaluedMapImpl();
postParams.putSingle("username", username);
postParams.putSingle("email", username + "@docs.com");
postParams.putSingle("password", "12345678");
postParams.putSingle("time_zone", "Asia/Tokyo");
ClientResponse response = userResource.put(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
// Logout admin
logout(adminAuthenticationToken);
}
/**
* Connects a user to the application.
*
* @param username Username
* @param password Password
* @param remember Remember user
* @return Authentication token
*/
public String login(String username, String password, Boolean remember) {
WebResource userResource = resource.path("/user/login");
MultivaluedMap<String, String> postParams = new MultivaluedMapImpl();
postParams.putSingle("username", username);
postParams.putSingle("password", password);
postParams.putSingle("remember", remember.toString());
ClientResponse response = userResource.post(ClientResponse.class, postParams);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
return getAuthenticationCookie(response);
}
/**
* Connects a user to the application.
*
* @param username Username
* @return Authentication token
*/
public String login(String username) {
return login(username, "12345678", false);
}
/**
* Disconnects a user from the application.
*
* @param authenticationToken Authentication token
*/
public void logout(String authenticationToken) {
WebResource userResource = resource.path("/user/logout");
userResource.addFilter(new CookieAuthenticationFilter(authenticationToken));
ClientResponse response = userResource.post(ClientResponse.class);
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
}
/**
* Extracts the authentication token of the response.
*
* @param response Response
* @return Authentication token
*/
public String getAuthenticationCookie(ClientResponse response) {
String authToken = null;
for (NewCookie cookie : response.getCookies()) {
if (TokenBasedSecurityFilter.COOKIE_NAME.equals(cookie.getName())) {
authToken = cookie.getValue();
}
}
return authToken;
}
}

View File

@ -0,0 +1,34 @@
package com.sismics.docs.rest.util;
import junit.framework.Assert;
import org.junit.Test;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.util.ValidationUtil;
/**
* Test the validations.
*
* @author jtremeaux
*/
public class TestValidationUtil {
@Test
public void testValidateHttpUrlFail() throws Exception {
ValidationUtil.validateHttpUrl("http://www.google.com", "url");
ValidationUtil.validateHttpUrl("https://www.google.com", "url");
ValidationUtil.validateHttpUrl(" https://www.google.com ", "url");
try {
ValidationUtil.validateHttpUrl("ftp://www.google.com", "url");
Assert.fail();
} catch (ClientException e) {
// NOP
}
try {
ValidationUtil.validateHttpUrl("http://", "url");
Assert.fail();
} catch (ClientException e) {
// NOP
}
}
}

325
docs-web/pom.xml Normal file
View File

@ -0,0 +1,325 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../docs-parent</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>docs-web</artifactId>
<packaging>war</packaging>
<name>Docs Web</name>
<dependencies>
<!-- Dependencies to Docs -->
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-core</artifactId>
</dependency>
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-web-common</artifactId>
</dependency>
<!-- Dependencies to Jersey -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-bundle</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
</dependency>
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-multipart</artifactId>
</dependency>
<!-- Other external dependencies -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-web-common</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.grizzly</groupId>
<artifactId>grizzly-servlet-webserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-grizzly2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.subethamail</groupId>
<artifactId>subethasmtp-wiser</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<configuration>
<systemProperties>
<systemProperty>
<name>webapp.root</name>
<value>${basedir}/src/main/webapp</value>
</systemProperty>
</systemProperties>
<scanIntervalSeconds>0</scanIntervalSeconds>
<webAppConfig>
<contextPath>/docs-web</contextPath>
<extraClasspath>target/classes;../docs-core/target/classes</extraClasspath>
<overrideDescriptor>src/dev/main/webapp/web-override.xml</overrideDescriptor>
</webAppConfig>
</configuration>
</plugin>
<!-- Cargo Plugin -->
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<configuration>
<container>
<containerId>jetty8x</containerId>
<type>remote</type>
</container>
<deployer>
<type>remote</type>
</deployer>
<deployables>
<deployable>
<groupId>com.sismics.docs</groupId>
<artifactId>docs-web</artifactId>
<type>war</type>
<properties>
<context>/docs</context>
</properties>
<pingURL>http://localhost:8080/docs/index.html</pingURL>
</deployable>
</deployables>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>com.samaxes.maven</groupId>
<artifactId>
minify-maven-plugin
</artifactId>
<versionRange>[1.5,)</versionRange>
<goals>
<goal>minify</goal>
</goals>
</pluginExecutionFilter>
<action>
<ignore />
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<profiles>
<!-- Development profile (active by default) -->
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>env</name>
<value>dev</value>
</property>
</activation>
<build>
<resources>
<resource>
<directory>src/dev/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/config.properties</exclude>
</excludes>
</resource>
<resource>
<directory>src/dev/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/config.properties</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<configuration>
<scanIntervalSeconds>0</scanIntervalSeconds>
<webAppConfig>
<contextPath>/docs-web</contextPath>
<extraClasspath>target/classes;../docs-core/target/classes</extraClasspath>
<overrideDescriptor>src/dev/main/webapp/web-override.xml</overrideDescriptor>
</webAppConfig>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- Production profile -->
<profile>
<id>prod</id>
<activation>
<property>
<name>env</name>
<value>prod</value>
</property>
</activation>
<build>
<resources>
<resource>
<directory>src/prod/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/config.properties</exclude>
</excludes>
</resource>
<resource>
<directory>src/prod/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/config.properties</include>
</includes>
</resource>
</resources>
<plugins>
<!-- Generation of the war -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warSourceExcludes>
sismicsdocs/**,
</warSourceExcludes>
<webXml>src\main\webapp\WEB-INF\web.xml</webXml>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- Hosted version profile -->
<profile>
<id>hosted</id>
<activation>
<property>
<name>env</name>
<value>hosted</value>
</property>
</activation>
<build>
<resources>
<resource>
<directory>src/hosted/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="docs"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<!-- Override init parameter to avoid nasty file locking issue on windows. -->
<servlet>
<servlet-name>default</servlet-name>
<init-param>
<param-name>useFileMappedBuffer</param-name>
<param-value>false</param-value>
</init-param>
</servlet>
</web-app>

View File

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

View File

@ -0,0 +1,10 @@
hibernate.connection.driver_class=org.hsqldb.jdbcDriver
hibernate.connection.url=jdbc:hsqldb:mem:docs
hibernate.connection.username=sa
hibernate.connection.password=
hibernate.hbm2ddl.auto=none
hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.show_sql=false
hibernate.format_sql=false
hibernate.max_fetch_depth=5
hibernate.cache.use_second_level_cache=false

View File

@ -0,0 +1,8 @@
log4j.rootCategory=WARN, CONSOLE, MEMORY
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{DATE} %p %l %m %n
log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
log4j.appender.MEMORY.size=1000
log4j.logger.com.sismics=DEBUG

View File

@ -0,0 +1,14 @@
package com.sismics.docs.rest.constant;
/**
* Base functions.
*
* @author jtremeaux
*/
public enum BaseFunction {
/**
* Allows the user to use the admin fonctions.
*/
ADMIN,
}

View File

@ -0,0 +1,114 @@
package com.sismics.docs.rest.resource;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Appender;
import org.apache.log4j.Logger;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sismics.docs.core.util.ConfigUtil;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.util.log4j.LogCriteria;
import com.sismics.util.log4j.LogEntry;
import com.sismics.util.log4j.MemoryAppender;
/**
* General app REST resource.
*
* @author jtremeaux
*/
@Path("/app")
public class AppResource extends BaseResource {
/**
* Return the information about the application.
*
* @return Response
* @throws JSONException
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response version() throws JSONException {
ResourceBundle configBundle = ConfigUtil.getConfigBundle();
String currentVersion = configBundle.getString("api.current_version");
String minVersion = configBundle.getString("api.min_version");
JSONObject response = new JSONObject();
response.put("current_version", currentVersion.replace("-SNAPSHOT", ""));
response.put("min_version", minVersion);
response.put("total_memory", Runtime.getRuntime().totalMemory());
response.put("free_memory", Runtime.getRuntime().freeMemory());
return Response.ok().entity(response).build();
}
/**
* Retrieve the application logs.
*
* @param level Filter on logging level
* @param tag Filter on logger name / tag
* @param message Filter on message
* @param limit Page limit
* @param offset Page offset
* @return
* @throws JSONException
*/
@GET
@Path("log")
@Produces(MediaType.APPLICATION_JSON)
public Response log(
@QueryParam("level") String level,
@QueryParam("tag") String tag,
@QueryParam("message") String message,
@QueryParam("limit") Integer limit,
@QueryParam("offset") Integer offset) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
checkBaseFunction(BaseFunction.ADMIN);
// Get the memory appender
Logger logger = Logger.getRootLogger();
Appender appender = logger.getAppender("MEMORY");
if (appender == null || !(appender instanceof MemoryAppender)) {
throw new ServerException("ServerError", "MEMORY appender not configured");
}
MemoryAppender memoryAppender = (MemoryAppender) appender;
// Find the logs
LogCriteria logCriteria = new LogCriteria();
logCriteria.setLevel(StringUtils.stripToNull(level));
logCriteria.setTag(StringUtils.stripToNull(tag));
logCriteria.setMessage(StringUtils.stripToNull(message));
PaginatedList<LogEntry> paginatedList = PaginatedLists.create(limit, offset);
memoryAppender.find(logCriteria, paginatedList);
JSONObject response = new JSONObject();
List<JSONObject> logs = new ArrayList<JSONObject>();
for (LogEntry logEntry : paginatedList.getResultList()) {
JSONObject log = new JSONObject();
log.put("date", logEntry.getTimestamp());
log.put("level", logEntry.getLevel());
log.put("tag", logEntry.getTag());
log.put("message", logEntry.getMessage());
logs.add(log);
}
response.put("total", paginatedList.getResultCount());
response.put("logs", logs);
return Response.ok().entity(response).build();
}
}

View File

@ -0,0 +1,82 @@
package com.sismics.docs.rest.resource;
import java.security.Principal;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import org.codehaus.jettison.json.JSONException;
import com.sismics.docs.rest.constant.BaseFunction;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.security.IPrincipal;
import com.sismics.security.UserPrincipal;
import com.sismics.util.filter.TokenBasedSecurityFilter;
/**
* Base class of REST resources.
*
* @author jtremeaux
*/
public abstract class BaseResource {
/**
* Injects the HTTP request.
*/
@Context
protected HttpServletRequest request;
/**
* Application key.
*/
@QueryParam("app_key")
protected String appKey;
/**
* Principal of the authenticated user.
*/
protected IPrincipal principal;
/**
* This method is used to check if the user is authenticated.
*
* @return True if the user is authenticated and not anonymous
*/
protected boolean authenticate() {
Principal principal = (Principal) request.getAttribute(TokenBasedSecurityFilter.PRINCIPAL_ATTRIBUTE);
if (principal != null && principal instanceof IPrincipal) {
this.principal = (IPrincipal) principal;
return !this.principal.isAnonymous();
} else {
return false;
}
}
/**
* Checks if the user has a base function. Throw an exception if the check fails.
*
* @param baseFunction Base function to check
* @throws JSONException
*/
protected void checkBaseFunction(BaseFunction baseFunction) throws JSONException {
if (!hasBaseFunction(baseFunction)) {
throw new ForbiddenClientException();
}
}
/**
* Checks if the user has a base function.
*
* @param baseFunction Base function to check
* @return True if the user has the base function
* @throws JSONException
*/
protected boolean hasBaseFunction(BaseFunction baseFunction) throws JSONException {
if (principal == null || !(principal instanceof UserPrincipal)) {
return false;
}
Set<String> baseFunctionSet = ((UserPrincipal) principal).getBaseFunctionSet();
return baseFunctionSet != null && baseFunctionSet.contains(baseFunction.name());
}
}

View File

@ -0,0 +1,230 @@
package com.sismics.docs.rest.resource;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.NoResultException;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.util.jpa.PaginatedList;
import com.sismics.docs.core.util.jpa.PaginatedLists;
import com.sismics.docs.core.util.jpa.SortCriteria;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.util.ValidationUtil;
/**
* Document REST resources.
*
* @author bgamard
*/
@Path("/document")
public class DocumentResource extends BaseResource {
/**
* Returns a document.
*
* @param id Document ID
* @return Response
* @throws JSONException
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response get(
@QueryParam("id") String id) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
DocumentDao documentDao = new DocumentDao();
Document documentDb = null;
try {
documentDb = documentDao.getDocument(id, principal.getId());
} catch (NoResultException e) {
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
}
JSONObject document = new JSONObject();
document.put("id", documentDb.getId());
document.put("title", documentDb.getTitle());
document.put("description", documentDb.getDescription());
document.put("create_date", documentDb.getCreateDate().getTime());
return Response.ok().entity(document).build();
}
/**
* Returns all documents.
*
* @param limit Page limit
* @param offset Page offset
* @return Response
* @throws JSONException
*/
@GET
@Path("list")
@Produces(MediaType.APPLICATION_JSON)
public Response list(
@QueryParam("limit") Integer limit,
@QueryParam("offset") Integer offset,
@QueryParam("sort_column") Integer sortColumn,
@QueryParam("asc") Boolean asc) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
JSONObject response = new JSONObject();
List<JSONObject> documents = new ArrayList<JSONObject>();
DocumentDao documentDao = new DocumentDao();
PaginatedList<DocumentDto> paginatedList = PaginatedLists.create(limit, offset);
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
DocumentCriteria documentCriteria = new DocumentCriteria();
documentCriteria.setUserId(principal.getId());
documentDao.findByCriteria(paginatedList, documentCriteria, sortCriteria);
for (DocumentDto documentDto : paginatedList.getResultList()) {
JSONObject document = new JSONObject();
document.put("id", documentDto.getId());
document.put("title", documentDto.getTitle());
document.put("description", documentDto.getDescription());
document.put("create_date", documentDto.getCreateTimestamp());
documents.add(document);
}
response.put("total", paginatedList.getResultCount());
response.put("documents", documents);
return Response.ok().entity(response).build();
}
/**
* Creates a new document.
*
* @param title Title
* @param description Description
* @return Response
* @throws JSONException
*/
@PUT
@Produces(MediaType.APPLICATION_JSON)
public Response add(
@FormParam("title") String title,
@FormParam("description") String description) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Validate input data
title = ValidationUtil.validateLength(title, "title", 1, 100, false);
description = ValidationUtil.validateLength(description, "description", 0, 4000, true);
// Create the document
DocumentDao documentDao = new DocumentDao();
Document document = new Document();
document.setUserId(principal.getId());
document.setTitle(title);
document.setDescription(description);
String documentId = documentDao.create(document);
JSONObject response = new JSONObject();
response.put("id", documentId);
return Response.ok().entity(response).build();
}
/**
* Updates the document.
*
* @param title Title
* @param description Description
* @return Response
* @throws JSONException
*/
@POST
@Path("{id: [a-z0-9\\-]+}")
@Produces(MediaType.APPLICATION_JSON)
public Response update(
@PathParam("id") String id,
@FormParam("title") String title,
@FormParam("description") String description) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Validate input data
title = ValidationUtil.validateLength(title, "title", 1, 100, false);
description = ValidationUtil.validateLength(description, "description", 0, 4000, true);
// Get the document
DocumentDao documentDao = new DocumentDao();
Document document = null;
try {
document = documentDao.getDocument(id, principal.getId());
} catch (NoResultException e) {
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
}
// Update the document
if (title != null) {
document.setTitle(title);
}
if (description != null) {
document.setDescription(description);
}
// Always return ok
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
/**
* Deletes a document.
*
* @param id Document ID
* @return Response
* @throws JSONException
*/
@DELETE
@Path("{id: [a-z0-9\\-]+}")
@Produces(MediaType.APPLICATION_JSON)
public Response delete(
@PathParam("id") String id) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the document
DocumentDao documentDao = new DocumentDao();
Document document = null;
try {
document = documentDao.getDocument(id, principal.getId());
} catch (NoResultException e) {
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
}
// Delete the document
documentDao.delete(document.getId());
// Always return ok
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
}

View File

@ -0,0 +1,200 @@
package com.sismics.docs.rest.resource;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.NoResultException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sismics.docs.core.dao.jpa.DocumentDao;
import com.sismics.docs.core.dao.jpa.FileDao;
import com.sismics.docs.core.model.jpa.Document;
import com.sismics.docs.core.model.jpa.File;
import com.sismics.docs.core.util.DirectoryUtil;
import com.sismics.rest.exception.ClientException;
import com.sismics.rest.exception.ForbiddenClientException;
import com.sismics.rest.exception.ServerException;
import com.sismics.rest.util.ValidationUtil;
import com.sismics.util.mime.MimeTypeUtil;
import com.sun.jersey.multipart.FormDataBodyPart;
import com.sun.jersey.multipart.FormDataParam;
/**
* File REST resources.
*
* @author bgamard
*/
@Path("/file")
public class FileResource extends BaseResource {
/**
* Add a file to a document.
*
* @param id Document ID
* @param fileBodyPart File to add
* @return Response
* @throws JSONException
*/
@PUT
@Consumes("multipart/form-data")
@Produces(MediaType.APPLICATION_JSON)
public Response add(
@FormDataParam("id") String documentId,
@FormDataParam("file") FormDataBodyPart fileBodyPart) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Validate input data
ValidationUtil.validateRequired(documentId, "id");
ValidationUtil.validateRequired(fileBodyPart, "file");
// Get the document
DocumentDao documentDao = new DocumentDao();
Document document = null;
try {
document = documentDao.getDocument(documentId, principal.getId());
} catch (NoResultException e) {
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
}
FileDao fileDao = new FileDao();
InputStream is = fileBodyPart.getValueAs(InputStream.class);
try {
// Create the file
File file = new File();
file.setDocumentId(document.getId());
file.setMimeType(MimeTypeUtil.guessMimeType(is));
String fileId = fileDao.create(file);
// Copy the incoming stream content into the storage directory
Files.copy(is, Paths.get(DirectoryUtil.getStorageDirectory().getPath(), fileId));
// Always return ok
JSONObject response = new JSONObject();
response.put("status", "ok");
response.put("id", fileId);
return Response.ok().entity(response).build();
} catch (Exception e) {
throw new ServerException("FileError", "Error adding a file", e);
}
}
/**
* Returns a file.
*
* @param id Document ID
* @return Response
* @throws JSONException
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response get(
@QueryParam("id") String id) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
FileDao fileDao = new FileDao();
File fileDb = null;
try {
fileDb = fileDao.getFile(id);
} catch (NoResultException e) {
throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", id));
}
JSONObject file = new JSONObject();
file.put("id", fileDb.getId());
file.put("mimetype", fileDb.getMimeType());
file.put("document_id", fileDb.getDocumentId());
file.put("create_date", fileDb.getCreateDate().getTime());
return Response.ok().entity(file).build();
}
/**
* Returns files linked to a document.
*
* @param id Document ID
* @return Response
* @throws JSONException
*/
@GET
@Path("list")
@Produces(MediaType.APPLICATION_JSON)
public Response list(
@QueryParam("id") String documentId) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
FileDao fileDao = new FileDao();
List<File> fileList = fileDao.getByDocumentId(documentId);
JSONObject response = new JSONObject();
List<JSONObject> files = new ArrayList<JSONObject>();
for (File fileDb : fileList) {
JSONObject file = new JSONObject();
file.put("id", fileDb.getId());
file.put("mimetype", fileDb.getMimeType());
file.put("document_id", fileDb.getDocumentId());
file.put("create_date", fileDb.getCreateDate().getTime());
files.add(file);
}
response.put("files", files);
return Response.ok().entity(response).build();
}
/**
* Deletes a file.
*
* @param id File ID
* @return Response
* @throws JSONException
*/
@DELETE
@Path("{id: [a-z0-9\\-]+}")
@Produces(MediaType.APPLICATION_JSON)
public Response delete(
@PathParam("id") String id) throws JSONException {
if (!authenticate()) {
throw new ForbiddenClientException();
}
// Get the file
FileDao fileDao = new FileDao();
File file = null;
try {
file = fileDao.getFile(id);
} catch (NoResultException e) {
throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", id));
}
// Delete the document
fileDao.delete(file.getId());
// Always return ok
JSONObject response = new JSONObject();
response.put("status", "ok");
return Response.ok().entity(response).build();
}
}

View File

@ -0,0 +1,46 @@
package com.sismics.docs.rest.resource;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import com.sismics.docs.core.dao.jpa.LocaleDao;
import com.sismics.docs.core.model.jpa.Locale;
/**
* Locale REST resources.
*
* @author jtremeaux
*/
@Path("/locale")
public class LocaleResource extends BaseResource {
/**
* Returns the list of all locales.
*
* @return Response
* @throws JSONException
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response list() throws JSONException {
LocaleDao localeDao = new LocaleDao();
List<Locale> localeList = localeDao.findAll();
JSONObject response = new JSONObject();
List<JSONObject> items = new ArrayList<JSONObject>();
for (Locale locale : localeList) {
JSONObject item = new JSONObject();
item.put("id", locale.getId());
items.add(item);
}
response.put("locales", items);
return Response.ok().entity(response).build();
}
}

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