args) {
+ return queryForList(sql, args, 0);
+ }
+
+ /**
+ * Query for an {@code int} passing in a SQL query using the named parameter support provided by the {@code NamedParameterJdbcTemplate} and a map containing
+ * the arguments.
+ *
+ * The query is expected to be a single row/single column query; the returned result will be directly mapped to an {@code int}.
+ *
+ * @param sql
+ * the SQL query to run
+ * @param args
+ * the map containing the arguments for the query
+ */
+ public int queryForInt(String sql, Map args) {
+ Integer value = getNamedParameterJdbcTemplate().queryForObject(sql, args, Integer.class);
+ return (value != null ? value.intValue() : 0);
+ }
+
+ /**
+ * Query for a {@code String} passing in a SQL query using the named parameter support provided by the {@code NamedParameterJdbcTemplate} and a map
+ * containing the arguments.
+ *
+ * The query is expected to be a single row/single column query; the returned result will be directly mapped to a {@code String}.
+ *
+ * @param sql
+ * the SQL query to run
+ * @param args
+ * the map containing the arguments for the query
+ */
+ public String queryForString(String sql, Map args) {
+ return getNamedParameterJdbcTemplate().queryForObject(sql, args, String.class);
+ }
+
+ /**
+ * Executes a batch using the supplied SQL statement with the batch of supplied arguments using the named parameter support provided by the
+ * {@code NamedParameterJdbcTemplate}
+ *
+ * @param sql
+ * the SQL statement to execute
+ * @param batchValues
+ * the array of Maps containing the batch of arguments for the query
+ *
+ * @return an array containing the numbers of rows affected by each update in the batch
+ */
+ public int[] executeBatchUpdate(String sql, Map[] batchValues) {
+ return getNamedParameterJdbcTemplate().batchUpdate(sql, batchValues);
+ }
+
+ /**
+ * Execute the supplied SQL statement with (optional) supplied arguments using the named parameter support provided by the
+ * {@code NamedParameterJdbcTemplate}
+ *
+ * @param sql
+ * the SQL statement to execute
+ * @param args
+ * the map containing the arguments for the query
+ *
+ * @return the number of rows affected by the update
+ */
+ public int executeUpdate(String sql, Map args) {
+ return getNamedParameterJdbcTemplate().update(sql, args);
+ }
+
+ /**
+ * Execute the supplied SQL statement with no arguments
+ *
+ * @param sql
+ * the SQL statement to execute
+ *
+ * @return the number of rows affected by the update
+ */
+ public int executeUpdate(String sql) {
+ return getJdbcTemplate().update(sql);
+ }
+
+ /**
+ * Executes SQL UPDATE statement with the supplied class and its ID and keyValuePairs
+ *
+ * @param clazz
+ * any class that extends BaseEntity
+ * @param keyValuePairs
+ * key value pairs, must include the ID field
+ *
+ * @return the number of rows affected by the update
+ */
+ public int executeUpdateByTable(String tableName, Map args) {
+
+ StringBuilder sql = new StringBuilder("UPDATE `" + tableName + "` SET ");
+ int i = 0;
+ for (String key : args.keySet()) {
+ if (i > 0) sql.append(", ");
+ sql.append(key + " = :" + key);
+ i++;
+ }
+ sql.append(" WHERE id = :id");
+
+ return executeUpdate(sql.toString(), args);
+ }
+
+ /**
+ * Execute SQL Insert statement with the supplied arguments
+ *
+ * @param tableName
+ * the database table name
+ * @param args
+ * the map containing the arguments for the insert statement in the form of column name and its value
+ *
+ * @return the number of rows affected as returned by the JDBC driver
+ */
+ public int executeInsert(String tableName, Map args) {
+ return getJdbcInsert(tableName, null).execute(args);
+ }
+
+ /**
+ * Execute SQL Insert statement with the supplied arguments and return the generated key value
+ *
+ * @param tableName
+ * the database table name
+ * @param idName
+ * the name of ID column that has auto generated key
+ * @param args
+ * the map containing the arguments for the insert statement in the form of column name and its value
+ *
+ * @return the generated key value
+ */
+ public Number executeInsertAndReturnKey(String tableName, String idName, Map args) {
+ return getJdbcInsert(tableName, idName).executeAndReturnKey(args);
+ }
+
+ /**
+ * Executes SQL Delete statement with the supplied class and its ID
+ *
+ * @param clazz
+ * any class that extends BaseEntity
+ * @param id
+ * the entity's ID
+ *
+ * @return the number of rows affected by the delete
+ */
+ public int executeDelete(Class extends BaseEntity> clazz, Serializable id) {
+ Table table = clazz.getAnnotation(Table.class);
+
+ String sql = "DELETE FROM " + table.name() + " WHERE id = :id";
+
+ return executeUpdate(sql, MapUtils.toHashMap(Params.ID, id));
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/engine/FreeMarkerEngine.java b/src/main/java/com/ffii/core/engine/FreeMarkerEngine.java
new file mode 100644
index 0000000..c4e9d0b
--- /dev/null
+++ b/src/main/java/com/ffii/core/engine/FreeMarkerEngine.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.engine;
+
+import java.io.IOException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
+import org.springframework.web.context.support.WebApplicationObjectSupport;
+import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
+
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+/**
+ * FreeMarker template engine - a Spring Component Bean
+ *
+ * @author Patrick
+ */
+@Component
+public class FreeMarkerEngine extends WebApplicationObjectSupport {
+
+ @Autowired
+ private FreeMarkerConfigurer freemarkerConfig;
+
+ /**
+ * Generates content from FreeMarker template
+ *
+ * @param template
+ * the FreeMarker template name (e.g. {@code "example/example.ftl"})
+ * @param model
+ * the model to be applied to the FreeMarker template, may be null
+ * @return the generated content as a {@code String}, or {@code null} if exceptions occured
+ */
+ public String generateFreeMarkerContent(String template, Object model) {
+ try {
+ Template t = freemarkerConfig.getConfiguration().getTemplate(template);
+ return FreeMarkerTemplateUtils.processTemplateIntoString(t, model);
+ } catch (TemplateException e) {
+ logger.error("Error while processing FreeMarker template ", e);
+ } catch (IOException e) {
+ logger.error("IOException occured ", e);
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/engine/MailEngine.java b/src/main/java/com/ffii/core/engine/MailEngine.java
new file mode 100644
index 0000000..fdd68f1
--- /dev/null
+++ b/src/main/java/com/ffii/core/engine/MailEngine.java
@@ -0,0 +1,316 @@
+/*******************************************************************************
+ * Copyright 2018 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.engine;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import com.ffii.core.Settings;
+import com.ffii.core.setting.service.SettingsService;
+import com.ffii.core.utils.SecurityUtils;
+import com.ffii.tbms.user.service.SysGroupService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.InputStreamSource;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.mail.javamail.JavaMailSenderImpl;
+import org.springframework.mail.javamail.MimeMessageHelper;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.support.WebApplicationObjectSupport;
+
+/**
+ * Mail Engine is a Spring Component Bean that handles E-mail operations.
+ *
+ * @author Patrick
+ */
+@Component
+public class MailEngine extends WebApplicationObjectSupport {
+
+ @Autowired
+ private FreeMarkerEngine freeMarkerEngine;
+
+ private JavaMailSender mailSender;
+
+ @Autowired
+ private SettingsService settingsService;
+
+ @Autowired
+ private SysGroupService sysGroupService;
+
+ /**
+ * Send HTML mail message
+ *
+ * @param from
+ * the from address, must not be null
+ * @param replyTo
+ * the reply to address, may be null
+ * @param to
+ * the to addresses, must not be null
+ * @param cc
+ * the cc addresses, may be null
+ * @param bcc
+ * the bcc addresses, may be null
+ * @param subject
+ * the subject, may be null
+ * @param html
+ * the html message body, must not be null
+ * @param attachmentFilename
+ * the name of the attachment as it will appear in the mail
+ * @param file
+ * the File to attach
+ */
+
+ private JavaMailSender getJavaMailSender(int sysGroupId){
+ //if(mailSender == null){
+ Map sysGroup = sysGroupService.find(sysGroupId);
+
+ String smtp_host = "";
+ int smtp_port = 0;
+ String smtp_username = "";
+ String smtp_password = "";
+ logger.info(sysGroup);
+ if(sysGroup != null){
+ smtp_host = sysGroup.get("smtp_host")==null?"":sysGroup.get("smtp_host").toString();
+ smtp_port = sysGroup.get("smtp_port")==null?0:Integer.parseInt(sysGroup.get("smtp_port").toString());
+ smtp_username = sysGroup.get("smtp_username")==null?"":sysGroup.get("smtp_username").toString();
+ smtp_password = sysGroup.get("smtp_password")==null?"":sysGroup.get("smtp_password").toString();
+ }
+
+
+ JavaMailSenderImpl jmsi = new JavaMailSenderImpl();
+ jmsi.setHost(smtp_host);
+ jmsi.setPort(smtp_port);
+ jmsi.setUsername(smtp_username);
+ jmsi.setPassword(smtp_password);
+
+ Properties props = jmsi.getJavaMailProperties();
+ props.put("mail.transport.protocol", "smtp");
+ props.put("mail.smtp.auth", "true");
+ props.put("mail.smtp.starttbms.enable", "true");
+ props.put("mail.debug", "true");
+ mailSender = jmsi;
+ //}
+ return mailSender;
+ }
+
+ public void sendHtmlMail(int sysGroupId, InternetAddress from, InternetAddress replyTo, InternetAddress[] to, InternetAddress[] cc, InternetAddress[] bcc,
+ String subject, String html, String attachmentFilename, File file) {
+
+ MimeMessage mimeMessage = getJavaMailSender(sysGroupId).createMimeMessage();
+
+ try {
+ MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8");
+ message.setFrom(from);
+ if (replyTo != null) message.setReplyTo(replyTo);
+ message.setTo(to);
+ if (cc != null) message.setCc(cc);
+ if (bcc != null) message.setBcc(bcc);
+ if (subject != null) message.setSubject(subject);
+ message.setText(html, true);
+ if (attachmentFilename != null && file != null) message.addAttachment(attachmentFilename, file);
+ } catch (MessagingException e) {
+ logger.error("Failed to send HTML mail", e);
+ }
+
+ getJavaMailSender(sysGroupId).send(mimeMessage);
+ }
+
+
+ /**
+ * Send HTML mail message
+ *
+ * @param from
+ * the from address, must not be null
+ * @param replyTo
+ * the reply to address, may be null
+ * @param to
+ * the to addresses, must not be null
+ * @param subject
+ * the subject, may be null
+ * @param html
+ * the html message body, must not be null
+ * @param attachmentFilename
+ * the name of the attachment as it will appear in the mail
+ * @param file
+ * the File to attach
+ */
+ public void sendHtmlMail(int sysGroupId, InternetAddress from, InternetAddress replyTo, InternetAddress[] to,
+ String subject, String html, String attachmentFilename, File file) {
+ sendHtmlMail(sysGroupId, from, replyTo, to, null, null, subject, html, attachmentFilename, file);
+ }
+
+ /**
+ * Send HTML mail message
+ *
+ * @param from
+ * the from address, must not be null
+ * @param replyTo
+ * the reply to address, may be null
+ * @param to
+ * the to addresses, must not be null
+ * @param subject
+ * the subject, may be null
+ * @param html
+ * the html message body, must not be null
+ */
+ public void sendHtmlMail(int sysGroupId,InternetAddress from, InternetAddress replyTo, InternetAddress[] to, String subject, String html) {
+ sendHtmlMail(sysGroupId, from, replyTo, to, null, null, subject, html, null, null);
+ }
+
+ /**
+ * Send HTML mail message generated from FreeMarker template
+ *
+ * @param from
+ * the from address, must not be null
+ * @param replyTo
+ * the reply to address, may be null
+ * @param to
+ * the to addresses, must not be null
+ * @param cc
+ * the cc addresses, may be null
+ * @param bcc
+ * the bcc addresses, may be null
+ * @param subject
+ * the subject, may be null
+ * @param template
+ * the FreeMarker template name, must not be null
+ * @param model
+ * the model to be applied to the FreeMarker template, may be null
+ * @param attachmentFilename
+ * the name of the attachment as it will appear in the mail
+ * @param file
+ * the File to attach
+ */
+ public void sendFreeMarkerHtmlMail(int sysGroupId,InternetAddress from, InternetAddress replyTo, InternetAddress[] to, InternetAddress[] cc, InternetAddress[] bcc,
+ String subject, String template, Map, ?> model, String attachmentFilename, File file) {
+
+ String html = freeMarkerEngine.generateFreeMarkerContent(template, model);
+
+ sendHtmlMail(sysGroupId,from, replyTo, to, cc, bcc, subject, html, attachmentFilename, file);
+ }
+
+ /**
+ * Send HTML mail message generated from FreeMarker template
+ *
+ * @param from
+ * the from address, must not be null
+ * @param replyTo
+ * the reply to address, may be null
+ * @param to
+ * the to addresses, must not be null
+ * @param subject
+ * the subject, may be null
+ * @param template
+ * the FreeMarker template name, must not be null
+ * @param model
+ * the model to be applied to the FreeMarker template, may be null
+ */
+ public void sendFreeMarkerHtmlMail(int sysGroupId, InternetAddress from, InternetAddress replyTo, InternetAddress[] to, InternetAddress[] cc, InternetAddress[] bcc,
+ String subject, String template, Map, ?> model) {
+ sendFreeMarkerHtmlMail(sysGroupId, from, replyTo, to, cc, bcc, subject, template, model, null, null);
+ }
+
+ /**
+ * Send HTML mail message generated from FreeMarker template
+ *
+ * @param from
+ * the from address, must not be null
+ * @param replyTo
+ * the reply to address, may be null
+ * @param to
+ * the to addresses, must not be null
+ * @param subject
+ * the subject, may be null
+ * @param template
+ * the FreeMarker template name, must not be null
+ * @param model
+ * the model to be applied to the FreeMarker template, may be null
+ * @param attachmentFilename
+ * the name of the attachment as it will appear in the mail
+ * @param file
+ * the File to attach
+ */
+ public void sendFreeMarkerHtmlMail(int sysGroupId,InternetAddress from, InternetAddress replyTo, InternetAddress[] to,
+ String subject, String template, Map, ?> model, String attachmentFilename, File file) {
+ sendFreeMarkerHtmlMail(sysGroupId, from, replyTo, to, null, null, subject, template, model, attachmentFilename, file);
+ }
+
+ /**
+ * Send HTML mail message generated from FreeMarker template
+ *
+ * @param from
+ * the from address, must not be null
+ * @param replyTo
+ * the reply to address, may be null
+ * @param to
+ * the to addresses, must not be null
+ * @param subject
+ * the subject, may be null
+ * @param template
+ * the FreeMarker template name, must not be null
+ * @param model
+ * the model to be applied to the FreeMarker template, may be null
+ * @param attachmentFilename
+ * the name of the attachment as it will appear in the mail
+ * @param file
+ * the File to attach
+ */
+
+ public void sendFreeMarkerHtmlMail(int sysGroupId, InternetAddress from, InternetAddress replyTo, InternetAddress[] to,
+ String subject, String template, Map, ?> model, String attachmentFilename, InputStreamSource file) {
+
+ String html = freeMarkerEngine.generateFreeMarkerContent(template, model);
+
+ MimeMessage mimeMessage = getJavaMailSender(sysGroupId).createMimeMessage();
+ try {
+ MimeMessageHelper message = new MimeMessageHelper(mimeMessage, true, "UTF-8");
+ message.setFrom(from);
+ if (replyTo != null) message.setReplyTo(replyTo);
+ message.setTo(to);
+ if (subject != null) message.setSubject(subject);
+ message.setText(html, true);
+ if (attachmentFilename != null && file != null) message.addAttachment(attachmentFilename, file);
+ } catch (MessagingException e) {
+ logger.error("Failed to send HTML mail", e);
+ }
+
+ getJavaMailSender(sysGroupId).send(mimeMessage);
+ }
+
+
+ /**
+ * Send HTML mail message generated from FreeMarker template
+ *
+ * @param from
+ * the from address, must not be null
+ * @param replyTo
+ * the reply to address, may be null
+ * @param to
+ * the to addresses, must not be null
+ * @param subject
+ * the subject, may be null
+ * @param template
+ * the FreeMarker template name, must not be null
+ * @param model
+ * the model to be applied to the FreeMarker template, may be null
+ */
+ public void sendFreeMarkerHtmlMail(int sysGroupId, InternetAddress from, InternetAddress replyTo, InternetAddress[] to,
+ String subject, String template, Map, ?> model) {
+ sendFreeMarkerHtmlMail(sysGroupId,from, replyTo, to, null, null, subject, template, model, null, null);
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/i18n/support/JdbcMessageSource.java b/src/main/java/com/ffii/core/i18n/support/JdbcMessageSource.java
new file mode 100644
index 0000000..4578055
--- /dev/null
+++ b/src/main/java/com/ffii/core/i18n/support/JdbcMessageSource.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.i18n.support;
+
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.support.AbstractMessageSource;
+
+import com.ffii.core.dao.JdbcDao;
+
+/**
+ * Jdbc MessageSource implementation.
+ *
+ *
+ * CREATE TABLE `i18n` (
+ * `locale` varchar(10) NOT NULL,
+ * `code` varchar(100) NOT NULL,
+ * `value` varchar(500) DEFAULT NULL,
+ * PRIMARY KEY (`locale`,`code`)
+ * );
+ *
+ *
+ * @author Patrick
+ */
+public class JdbcMessageSource extends AbstractMessageSource {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ @Override
+ protected String resolveCodeWithoutArguments(String code, Locale locale) {
+ return getMessages(locale.toString()).get(code);
+ }
+
+ @Override
+ protected MessageFormat resolveCode(String code, Locale locale) {
+ String msg = getMessages(locale.toString()).get(code);
+ return createMessageFormat(msg, locale);
+ }
+
+ /** i18n messages cache */
+ private static Map> MESSAGES = new HashMap>();
+
+ /**
+ * Load a list of messages from i18n table in database
+ *
+ * @param locale
+ * the locale String (e.g. {@code en_US})
+ */
+ public List> loadMessages(String locale) {
+ /* prep args */
+ Map args = new HashMap();
+ args.put("locale", locale);
+
+ /* build sql */
+ String sql = "SELECT locale, code, value FROM i18n WHERE locale = :locale";
+
+ /* do query and return */
+ return jdbcDao.queryForList(sql, args);
+ }
+
+ /**
+ * Get i18n messages map
+ *
+ * @param locale
+ * the locale String (e.g. {@code en_US})
+ */
+ public Map getMessages(String locale) {
+ Map messages = MESSAGES.get(locale);
+
+ /* if not loaded yet, load it from database */
+ if (messages == null) {
+ messages = new HashMap();
+
+ /* convert the list to a simple messages map */
+ List> rawMessages = loadMessages(locale);
+ for (Map rm : rawMessages) {
+ messages.put((String) rm.get("code"), (String) rm.get("value"));
+ }
+
+ /* cache the messages */
+ MESSAGES.put(locale, messages);
+ }
+
+ return messages;
+ }
+
+ /**
+ * Reset the i18n messages cache
+ */
+ public void resetMessagesCache() {
+ /* simply create a new map */
+ MESSAGES = new HashMap>();
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/security/authentication/AuthToken.java b/src/main/java/com/ffii/core/security/authentication/AuthToken.java
new file mode 100644
index 0000000..c9746d9
--- /dev/null
+++ b/src/main/java/com/ffii/core/security/authentication/AuthToken.java
@@ -0,0 +1,52 @@
+package com.ffii.core.security.authentication;
+
+import com.ffii.core.User;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+
+/**
+ * Authentication Token
+ *
+ * @author Patrick
+ */
+public class AuthToken extends AbstractAuthenticationToken {
+
+ private static final long serialVersionUID = 3383254131623375507L;
+
+ private final String token;
+ private final User user;
+
+ public AuthToken(String token) {
+ super(null);
+
+ this.token = token;
+ this.user = null;
+ setAuthenticated(false);
+ }
+
+ public AuthToken(String token, User user) {
+ super(user.getAuthorities());
+
+ this.token = token;
+ this.user = user;
+ setAuthenticated(true);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return getToken();
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return getUser();
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public User getUser() {
+ return user;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/security/authentication/TokenAuthenticationProvider.java b/src/main/java/com/ffii/core/security/authentication/TokenAuthenticationProvider.java
new file mode 100644
index 0000000..ed12605
--- /dev/null
+++ b/src/main/java/com/ffii/core/security/authentication/TokenAuthenticationProvider.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.security.authentication;
+
+import com.ffii.core.User;
+import com.ffii.core.security.service.TokenUserDetailsService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.stereotype.Component;
+
+/**
+ * Token Authentication Provider
+ *
+ * @author Patrick
+ */
+@Component
+public class TokenAuthenticationProvider implements AuthenticationProvider {
+
+ @Autowired
+ private TokenUserDetailsService tokenUserDetailsService;
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ final AuthToken authToken = (AuthToken) authentication;
+ final String token = authToken.getToken();
+
+ User user = tokenUserDetailsService.findUserByToken(token);
+
+ if (user == null)
+ throw new BadCredentialsException("No user found for token - " + token);
+
+ return new AuthToken(token, user);
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return authentication.equals(AuthToken.class);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/security/filter/AuthTokenFilter.java b/src/main/java/com/ffii/core/security/filter/AuthTokenFilter.java
new file mode 100644
index 0000000..f0174a1
--- /dev/null
+++ b/src/main/java/com/ffii/core/security/filter/AuthTokenFilter.java
@@ -0,0 +1,74 @@
+package com.ffii.core.security.filter;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ffii.core.security.authentication.AuthToken;
+import com.ffii.core.utils.StringUtils;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+
+/**
+ * Authentication Token Filter
+ *
+ * @author Patrick
+ */
+public class AuthTokenFilter extends AbstractAuthenticationProcessingFilter {
+
+ public static final String TOKEN_HEADER = "x-auth-token";
+
+ public AuthTokenFilter(RequestMatcher requestMatcher) {
+ super(requestMatcher);
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ final String tokenValue = getTokenValue((HttpServletRequest) request);
+
+ // This filter only applies if the header is present
+ if (StringUtils.isEmpty(tokenValue)) {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ // On success keep going on the chain
+ this.setAuthenticationSuccessHandler((request1, response1, authentication) -> {
+ chain.doFilter(request1, response1);
+ });
+
+ super.doFilter(request, response, chain);
+ }
+
+ @Override
+ public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
+ throws AuthenticationException, IOException, ServletException {
+ final String tokenValue = getTokenValue(request);
+
+ if (StringUtils.isEmpty(tokenValue)) {
+ return null;
+ }
+
+ AuthToken token = new AuthToken(tokenValue);
+ token.setDetails(authenticationDetailsSource.buildDetails(request));
+
+ return this.getAuthenticationManager().authenticate(token);
+ }
+
+ private String getTokenValue(HttpServletRequest request) {
+ return Collections.list(request.getHeaderNames()).stream()
+ .filter(header -> TOKEN_HEADER.equalsIgnoreCase(header)).map(header -> request.getHeader(header))
+ .findFirst().orElse(null);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/security/service/LoginLogService.java b/src/main/java/com/ffii/core/security/service/LoginLogService.java
new file mode 100644
index 0000000..dc54608
--- /dev/null
+++ b/src/main/java/com/ffii/core/security/service/LoginLogService.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.security.service;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.web.AbstractService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Login Log Service
+ *
+ * @author Patrick
+ */
+@Service
+public class LoginLogService extends AbstractService {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ /**
+ * Create Login Log record
+ *
+ * @param username
+ * the username
+ * @param remoteAddr
+ * request.getRemoteAddr()
+ * @param success
+ * true if the login is successful, else false
+ *
+ * @return true if the number of rows inserted is 1
+ */
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = false)
+ public boolean createLoginLog(String username, String remoteAddr, boolean success) {
+ Map args = new HashMap<>(4);
+ args.put("username", username);
+ args.put("loginTime", new Date());
+ args.put("ipAddr", remoteAddr);
+ args.put("success", success);
+
+ return (jdbcDao.executeInsert("user_login_log", args) == 1);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public List> listLastLog(String username, int limit) {
+ return jdbcDao.queryForList("SELECT success FROM user_login_log where username = :username ORDER BY loginTime DESC LIMIT " + limit,
+ MapUtils.toHashMap("username", username));
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/security/service/TokenUserDetailsService.java b/src/main/java/com/ffii/core/security/service/TokenUserDetailsService.java
new file mode 100644
index 0000000..77ba37d
--- /dev/null
+++ b/src/main/java/com/ffii/core/security/service/TokenUserDetailsService.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.security.service;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.ffii.core.User;
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.web.AbstractService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.support.MessageSourceAccessor;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.SpringSecurityMessageSource;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Token User Details Service
+ *
+ * @author Patrick
+ */
+@Service
+public class TokenUserDetailsService extends AbstractService {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ public static final String FIND_USER_BY_DEVICE_TOKEN_SQL = "SELECT u.* FROM users u LEFT JOIN access_token at ON u.id = at.userId"
+ + " WHERE u.deleted = 0 AND at.token = :token";
+
+ public static final String LOAD_AUTHORITIES_BY_USERNAME_SQL = "SELECT u.username, ua.authority"
+ + " FROM `users_authorities` ua" + " LEFT JOIN `users` u ON ua.userId = u.id"
+ + " WHERE u.deleted = 0 AND u.username = ?";
+
+ public static final String LOAD_GROUP_AUTHORITIES_BY_USERNAME_SQL = "SELECT g.id, g.name, ga.authority"
+ + " FROM `groups_authorities` ga" + " LEFT JOIN `groups` g ON ga.groupId = g.id AND g.deleted = 0"
+ + " LEFT JOIN `groups_users` gu ON ga.groupId = gu.groupId" + " LEFT JOIN `users` u ON gu.userId = u.id"
+ + " WHERE u.deleted = 0 AND u.username = ?";
+
+ protected final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
+
+ // ~ Constructors
+ public TokenUserDetailsService() {
+ }
+
+ // ~ Methods
+
+ /**
+ * Allows subclasses to add their own granted authorities to the list to be
+ * returned in the UserDetails .
+ *
+ * @param username the username, for use by finder methods
+ * @param authorities the current granted authorities, as populated from the
+ * authoritiesByUsername
mapping
+ */
+ protected void addCustomAuthorities(String username, List authorities) {
+ // add ROLE_USER for basic access
+ authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
+ }
+
+ public UserDetails loadUserByToken(String token) {
+
+ User user = findUserByToken(token); // contains no GrantedAuthority[]
+
+ // build GrantedAuthority[]
+ Set authoritiesSet = new HashSet<>();
+
+ // aadd all user's authorities
+ authoritiesSet.addAll(loadUserAuthorities(user.getUsername()));
+
+ // add all user's groups' authorities
+ authoritiesSet.addAll(loadGroupAuthorities(user.getUsername()));
+
+ // convert to List
+ List authoritiesList = new ArrayList(authoritiesSet);
+
+ addCustomAuthorities(user.getUsername(), authoritiesList);
+
+ user.setAuthorities(authoritiesList);
+
+ return user;
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public User findUserByToken(String token) {
+ List users = jdbcDao.query(FIND_USER_BY_DEVICE_TOKEN_SQL, User.class, MapUtils.toHashMap("token", token),
+ 1);
+ if (users.size() == 1)
+ return users.get(0);
+ else
+ return null;
+ }
+
+ /**
+ * Loads user authorities by executing SQL
+ *
+ * @return a list of GrantedAuthority objects for the user
+ */
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ protected List loadUserAuthorities(String username) {
+ return jdbcDao.getNamedParameterJdbcTemplate().query(LOAD_AUTHORITIES_BY_USERNAME_SQL,
+ MapUtils.toHashMap("username", username), new RowMapper() {
+ @Override
+ public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new SimpleGrantedAuthority(rs.getString("authority"));
+ }
+ });
+ }
+
+ /**
+ * Loads group authorities by executing SQL
+ *
+ * @return a list of GrantedAuthority objects from the user's groups
+ */
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ protected List loadGroupAuthorities(String username) {
+ return jdbcDao.getNamedParameterJdbcTemplate().query(LOAD_GROUP_AUTHORITIES_BY_USERNAME_SQL,
+ MapUtils.toHashMap("username", username), new RowMapper() {
+ @Override
+ public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new SimpleGrantedAuthority(rs.getString("authority"));
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/security/userdetails/UserDetailsServiceImpl.java b/src/main/java/com/ffii/core/security/userdetails/UserDetailsServiceImpl.java
new file mode 100644
index 0000000..fd85d56
--- /dev/null
+++ b/src/main/java/com/ffii/core/security/userdetails/UserDetailsServiceImpl.java
@@ -0,0 +1,239 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.security.userdetails;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.ffii.core.User;
+
+import org.springframework.context.ApplicationContextException;
+import org.springframework.context.support.MessageSourceAccessor;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.support.JdbcDaoSupport;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.SpringSecurityMessageSource;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.util.Assert;
+
+
+public class UserDetailsServiceImpl extends JdbcDaoSupport implements UserDetailsService {
+
+ // ~ Static fields/initializers =====================================================================================
+
+ public static final String DEF_USERS_BY_USERNAME_QUERY = "SELECT *"
+ + " FROM `users` WHERE deleted = 0 AND username = ?";
+
+ public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY = "SELECT u.username, ua.authority"
+ + " FROM `users_authorities` ua"
+ + " LEFT JOIN `users` u ON ua.userId = u.id"
+ + " WHERE u.deleted = 0 AND u.username = ?";
+
+ public static final String DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY = "SELECT g.id, g.name, ga.authority"
+ + " FROM `groups_authorities` ga"
+ + " LEFT JOIN `groups` g ON ga.groupId = g.id AND g.deleted = 0"
+ + " LEFT JOIN `groups_users` gu ON ga.groupId = gu.groupId"
+ + " LEFT JOIN `users` u ON gu.userId = u.id"
+ + " WHERE u.deleted = 0 AND u.username = ?";
+
+ // ~ Instance fields ================================================================================================
+
+ protected final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
+
+ private String usersByUsernameQuery;
+ private String authoritiesByUsernameQuery;
+ private String groupAuthoritiesByUsernameQuery;
+
+ private boolean enableAuthorities = true;
+ private boolean enableGroups;
+
+ // ~ Constructors ===================================================================================================
+
+ public UserDetailsServiceImpl() {
+ usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY;
+ authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY;
+ groupAuthoritiesByUsernameQuery = DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY;
+ }
+
+ // ~ Methods ========================================================================================================
+
+ /**
+ * Allows subclasses to add their own granted authorities to the list to be returned in the UserDetails .
+ *
+ * @param username
+ * the username, for use by finder methods
+ * @param authorities
+ * the current granted authorities, as populated from the authoritiesByUsername
mapping
+ */
+ protected void addCustomAuthorities(String username, List authorities) {
+ // add ROLE_USER for basic access
+ authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
+ }
+
+ public String getUsersByUsernameQuery() {
+ return usersByUsernameQuery;
+ }
+
+ @Override
+ protected void initDao() throws ApplicationContextException {
+ Assert.isTrue(enableAuthorities || enableGroups, "Use of either authorities or groups must be enabled");
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+
+ List users = loadUsersByUsername(username);
+
+ if (users.size() == 0) {
+ logger.debug("Query returned no results for user '" + username + "'");
+
+ throw new UsernameNotFoundException(
+ messages.getMessage("JdbcDaoImpl.notFound", new Object[] { username }, "Username {0} not found"));
+ }
+
+ User user = users.get(0); // contains no GrantedAuthority[]
+
+ Set dbAuthsSet = new HashSet();
+
+ if (enableAuthorities) {
+ dbAuthsSet.addAll(loadUserAuthorities(user.getUsername()));
+ }
+
+ if (enableGroups) {
+ dbAuthsSet.addAll(loadGroupAuthorities(user.getUsername()));
+ }
+
+ List dbAuths = new ArrayList(dbAuthsSet);
+
+ addCustomAuthorities(user.getUsername(), dbAuths);
+
+ if (dbAuths.size() == 0) {
+ logger.debug("User '" + username + "' has no authorities and will be treated as 'not found'");
+
+ throw new UsernameNotFoundException(
+ messages.getMessage("USER.noAuthority", new Object[] { username }, "User {0} has no GrantedAuthority"));
+ }
+
+ user.setAuthorities(dbAuths);
+ return user;
+ }
+
+ /**
+ * Executes the SQL usersByUsernameQuery and returns a list of UserDetails objects. There should normally only be one matching user.
+ */
+ protected List loadUsersByUsername(String username) {
+ return getJdbcTemplate().query(usersByUsernameQuery, new String[] { username }, BeanPropertyRowMapper.newInstance(User.class));
+ }
+
+ /**
+ * Loads authorities by executing the SQL from authoritiesByUsernameQuery .
+ *
+ * @return a list of GrantedAuthority objects for the user
+ */
+ protected List loadUserAuthorities(String username) {
+ return getJdbcTemplate().query(authoritiesByUsernameQuery, new String[] { username }, new RowMapper() {
+ @Override
+ public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new SimpleGrantedAuthority(rs.getString("authority"));
+ }
+ });
+ }
+
+ /**
+ * Loads authorities by executing the SQL from groupAuthoritiesByUsernameQuery .
+ *
+ * @return a list of GrantedAuthority objects for the user
+ */
+ protected List loadGroupAuthorities(String username) {
+ return getJdbcTemplate().query(groupAuthoritiesByUsernameQuery, new String[] { username }, new RowMapper() {
+ @Override
+ public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
+ return new SimpleGrantedAuthority(rs.getString("authority"));
+ }
+ });
+ }
+
+ /**
+ * Allows the default query string used to retrieve authorities based on username to be overridden, if default table or column names need to be changed. The
+ * default query is {@link #DEF_AUTHORITIES_BY_USERNAME_QUERY}; when modifying this query, ensure that all returned columns are mapped back to the same
+ * column names as in the default query.
+ *
+ * @param queryString
+ * The SQL query string to set
+ */
+ public void setAuthoritiesByUsernameQuery(String queryString) {
+ authoritiesByUsernameQuery = queryString;
+ }
+
+ protected String getAuthoritiesByUsernameQuery() {
+ return authoritiesByUsernameQuery;
+ }
+
+ /**
+ * Allows the default query string used to retrieve group authorities based on username to be overridden, if default table or column names need to be
+ * changed. The default query is {@link #DEF_GROUP_AUTHORITIES_BY_USERNAME_QUERY}; when modifying this query, ensure that all returned columns are mapped
+ * back to the same column names as in the default query.
+ *
+ * @param queryString
+ * The SQL query string to set
+ */
+ public void setGroupAuthoritiesByUsernameQuery(String queryString) {
+ groupAuthoritiesByUsernameQuery = queryString;
+ }
+
+ /**
+ * Allows the default query string used to retrieve users based on username to be overridden, if default table or column names need to be changed. The
+ * default query is {@link #DEF_USERS_BY_USERNAME_QUERY}; when modifying this query, ensure that all returned columns are mapped back to the same column
+ * names as in the default query. If the 'enabled' column does not exist in the source database, a permanent true value for this column may be returned by
+ * using a query similar to
+ *
+ *
+ * "select username,password,'true' as enabled from users where username = ?"
+ *
+ *
+ * @param usersByUsernameQueryString
+ * The query string to set
+ */
+ public void setUsersByUsernameQuery(String usersByUsernameQueryString) {
+ this.usersByUsernameQuery = usersByUsernameQueryString;
+ }
+
+ protected boolean getEnableAuthorities() {
+ return enableAuthorities;
+ }
+
+ /**
+ * Enables loading of authorities from the authorities table. Defaults to true
+ */
+ public void setEnableAuthorities(boolean enableAuthorities) {
+ this.enableAuthorities = enableAuthorities;
+ }
+
+ protected boolean getEnableGroups() {
+ return enableGroups;
+ }
+
+ /**
+ * Enables support for group authorities. Defaults to false
+ *
+ * @param enableGroups
+ */
+ public void setEnableGroups(boolean enableGroups) {
+ this.enableGroups = enableGroups;
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/security/web/authentication/JsonAuthenticationFailureHandler.java b/src/main/java/com/ffii/core/security/web/authentication/JsonAuthenticationFailureHandler.java
new file mode 100644
index 0000000..be29efb
--- /dev/null
+++ b/src/main/java/com/ffii/core/security/web/authentication/JsonAuthenticationFailureHandler.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.security.web.authentication;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
+
+import com.ffii.core.utils.BooleanUtils;
+import com.ffii.core.utils.JsonUtils;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.StringUtils;
+import com.ffii.core.utils.web.ServletRequestUtils;
+import com.ffii.core.utils.web.ServletResponseUtils;
+import com.ffii.core.web.view.AbstractView;
+import com.ffii.core.security.service.LoginLogService;
+import com.ffii.tbms.user.service.UserService;
+
+/**
+ * AuthenticationFailureHandler implementation which return a failure JSON String upon failed authentication.
+ *
+ * @author Patrick
+ */
+public class JsonAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
+
+ /** Continuous fail 5 time, lock user */
+ private final int TIMES = 5;
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private LoginLogService loginLogService;
+
+ /**
+ * Returns the failure JSON String to client.
+ */
+ @Override
+ public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
+ throws IOException, ServletException {
+
+ logger.info("Authentication Failure: " + exception.getMessage() + " [" + request.getRemoteAddr() + "]");
+
+ String username = StringUtils.left(ServletRequestUtils.getStringParameter(request, "username"), 32);
+
+ ServletResponseUtils.disableCaching(response);
+ response.setContentType(AbstractView.CONTENT_TYPE_JSON);
+ response.setCharacterEncoding("UTF-8");
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // set HTTP status to 401
+
+ boolean locked = false;
+ // log failed login
+ if (username != null) {
+ loginLogService.createLoginLog(username, request.getRemoteAddr(), false);
+
+ // when failed 5 times, lock account
+ List> logs = loginLogService.listLastLog(username, TIMES);
+ if (logs.size() >= TIMES) {
+ boolean needLock = true;
+ for (Map log : logs) {
+ if (BooleanUtils.isTrue(log.get("success"))) {
+ needLock = false;
+ break;
+ }
+ }
+ if (needLock) {
+ locked = true;
+ userService.lockUser(username, true);
+ }
+ }
+
+ }
+ Map result = MapUtils.toHashMap(
+ Params.SUCCESS, Boolean.FALSE,
+ Params.MSG,
+ locked ? "Account locked (" + TIMES + " Times Failure), please contact your IT administrator." : "Invalid Username or Password.");
+
+ ServletResponseUtils.writeStringToStream(response, AbstractView.CHARSET_UTF8, JsonUtils.toJsonString(result));
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/security/web/authentication/JsonAuthenticationSuccessHandler.java b/src/main/java/com/ffii/core/security/web/authentication/JsonAuthenticationSuccessHandler.java
new file mode 100644
index 0000000..c00ac19
--- /dev/null
+++ b/src/main/java/com/ffii/core/security/web/authentication/JsonAuthenticationSuccessHandler.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.security.web.authentication;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.ffii.core.Session;
+import com.ffii.core.Settings;
+import com.ffii.core.security.service.LoginLogService;
+import com.ffii.core.setting.service.SettingsService;
+import com.ffii.core.utils.JsonUtils;
+import com.ffii.core.utils.LocaleUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.SecurityUtils;
+import com.ffii.core.utils.web.ServletResponseUtils;
+import com.ffii.core.web.view.AbstractView;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
+import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
+import org.springframework.security.web.savedrequest.RequestCache;
+import org.springframework.security.web.savedrequest.SavedRequest;
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.i18n.SessionLocaleResolver;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * AuthenticationSuccessHandler implementation which return a success JSON String upon successful authentication.
+ *
+ * @author Patrick
+ */
+public class JsonAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
+
+ private RequestCache requestCache = new HttpSessionRequestCache();
+
+ @Autowired
+ private LoginLogService loginLogService;
+
+ @Autowired
+ private SettingsService settingsService;
+
+ /**
+ * Returns the successful JSON String to client, unless the account is locked.
+ */
+ @Override
+ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
+ throws IOException, ServletException {
+
+ logger.info("Authentication Success: " + authentication.getName() + " [" + request.getRemoteAddr() + "]");
+
+ ServletResponseUtils.disableCaching(response);
+ response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
+ response.setCharacterEncoding("UTF-8");
+
+ // save available locales to session
+ String[] availableLocales = settingsService.getString(Settings.SYS_AVAILABLE_LOCALES).split(",");
+ Session.setAttribute(request, Session.AVAILABLE_LOCALES, availableLocales);
+
+ // set user's default locale
+ String locale = SecurityUtils.getUser().getLocale();
+ if (!StringUtils.isEmpty(locale))
+ WebUtils.setSessionAttribute(request, SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, LocaleUtils.toLocale(locale));
+
+ // extra values to be passed to client
+ Map values = new HashMap();
+
+ SavedRequest savedRequest = requestCache.getRequest(request, response);
+ if (savedRequest != null) {
+ String targetUrlParameter = getTargetUrlParameter();
+ if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
+ requestCache.removeRequest(request, response);
+ authenticationSuccess(request, response, null);
+ return;
+ }
+
+ // Use the DefaultSavedRequest URL
+ String targetUrl = savedRequest.getRedirectUrl();
+ logger.debug("Sending DefaultSavedRequest Url: " + targetUrl);
+
+ // target URL to redirect to (if any)
+ values.put("targetUrl", URLEncoder.encode(targetUrl, "UTF-8"));
+ }
+
+ authenticationSuccess(request, response, values);
+ }
+
+ protected void authenticationSuccess(HttpServletRequest request, HttpServletResponse response, Map values)
+ throws UnsupportedEncodingException, IOException {
+
+ Map args = new HashMap();
+ args.put(Params.SUCCESS, Boolean.TRUE);
+ if (values != null) args.putAll(values);
+
+ clearAuthenticationAttributes(request);
+ ServletResponseUtils.writeStringToStream(response, AbstractView.CHARSET_UTF8, JsonUtils.toJsonString(args));
+
+ // log successful login
+ loginLogService.createLoginLog(SecurityUtils.getUser().getUsername(), request.getRemoteAddr(), true);
+
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/setting/Setting.java b/src/main/java/com/ffii/core/setting/Setting.java
new file mode 100644
index 0000000..9450546
--- /dev/null
+++ b/src/main/java/com/ffii/core/setting/Setting.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.setting;
+
+import java.io.Serializable;
+
+public class Setting implements Serializable {
+
+ private static final long serialVersionUID = 3925955875238868120L;
+
+ private String category;
+
+ private String type;
+
+ private String name;
+
+ private String value;
+
+ /** default constructor */
+ public Setting() {
+
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public void setCategory(String category) {
+ this.category = category;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/setting/service/SettingsService.java b/src/main/java/com/ffii/core/setting/service/SettingsService.java
new file mode 100644
index 0000000..7de097c
--- /dev/null
+++ b/src/main/java/com/ffii/core/setting/service/SettingsService.java
@@ -0,0 +1,169 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.setting.service;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.setting.Setting;
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.Params;
+
+/**
+ * System Settings Service
+ *
+ * @author Patrick
+ */
+@Service
+public class SettingsService {
+
+ private static final String SQL_LOAD_SETTINGS = "SELECT * FROM settings ORDER BY category, name";
+
+ private static final String SQL_INSERT_OR_UPDATE_SETTING = "INSERT INTO settings (`name`, `value`) VALUES (:name, :value) ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `value` = VALUES(`value`)";
+
+ /**
+ * Settings cache
+ */
+ private static Map SETTINGS = null;
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public List> searchSettings(Map args) {
+
+ StringBuilder sql = new StringBuilder("SELECT * FROM settings WHERE 1=1");
+
+ if (args.containsKey("category")) sql.append(" AND category = :category");
+ if (args.containsKey("type")) sql.append(" AND type = :type");
+ if (args.containsKey("name")) sql.append(" AND name = :name");
+
+ sql.append(" ORDER BY category, name");
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ /**
+ * Load and return a Map of all system settings from database
+ */
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public Map loadSettingsMap() {
+ List settings = jdbcDao.query(SQL_LOAD_SETTINGS, Setting.class, null, 0);
+ Map settingsMap = new HashMap();
+ for (Setting setting : settings) {
+ settingsMap.put(setting.getName(), setting);
+ }
+ return settingsMap;
+ }
+
+ /**
+ * @param name
+ * the name of the setting
+ * @param defaultValue
+ * the default value to return if setting is not found
+ *
+ * @return the String value of the setting, or the default value if not found
+ */
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public String getString(String name, String defaultValue) {
+ if (SETTINGS == null) SETTINGS = loadSettingsMap();
+ return SETTINGS.get(name) != null ? SETTINGS.get(name).getValue() : defaultValue;
+ }
+
+ /**
+ * @param name
+ * the name of the setting
+ *
+ * @return the String value of the setting, or {@code null} if not found
+ */
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public String getString(String name) {
+ return getString(name, null);
+ }
+
+ /**
+ * @param name
+ * the name of the setting
+ * @param defaultValue
+ * the default value to return if setting is not found
+ *
+ * @return the int value of the setting, or the default value if not found
+ */
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public int getInt(String name, int defaultValue) {
+ return NumberUtils.toInt(getString(name), defaultValue);
+ }
+
+ /**
+ * @param name
+ * the name of the setting
+ *
+ * @return the int value of the setting, or zero (0) if not found
+ */
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public int getInt(String name) {
+ return getInt(name, 0);
+ }
+
+ /**
+ * @param name
+ * the name of the setting
+ * @param defaultValue
+ * the default value to return if setting is not found
+ *
+ * @return the double value of the setting, or the default value if not found
+ */
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public double getDouble(String name, double defaultValue) {
+ return NumberUtils.toDouble(getString(name), defaultValue);
+ }
+
+ /**
+ * @param name
+ * the name of the setting
+ *
+ * @return the double value of the setting, or zero (0.0) if not found
+ */
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public double getDouble(String name) {
+ return getDouble(name, 0.0d);
+ }
+
+ /**
+ * @param name
+ * the name of the setting
+ * @param value
+ * the value of the setting
+ */
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public int saveSetting(String name, String value) {
+ Map args = new HashMap(2);
+ args.put(Params.NAME, name);
+ args.put(Params.VALUE, value);
+ return jdbcDao.executeUpdate(SQL_INSERT_OR_UPDATE_SETTING, args);
+ }
+
+ /**
+ * Reset the settings cache
+ */
+ public void resetSettingsCache() {
+ // simply set the settings cache to null
+ SETTINGS = null;
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/support/SimpleDateEditor.java b/src/main/java/com/ffii/core/support/SimpleDateEditor.java
new file mode 100644
index 0000000..fd6b521
--- /dev/null
+++ b/src/main/java/com/ffii/core/support/SimpleDateEditor.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.support;
+
+import java.beans.PropertyEditorSupport;
+import java.util.Date;
+
+import org.springframework.util.StringUtils;
+
+import com.ffii.core.utils.DateUtils;
+
+/**
+ * Property Editor for Date in the format defined by {@link DateUtils#PARSE_PATTERNS}
+ *
+ * Defaults to null
if cannot be parsed, never throw an Exception
+ *
+ *
+ * @see DateUtils#parseDateStrictly(String, String[], Date)
+ *
+ * @author Patrick
+ */
+public class SimpleDateEditor extends PropertyEditorSupport {
+
+ @Override
+ public void setAsText(String text) throws IllegalArgumentException {
+ if (StringUtils.hasText(text))
+ setValue(DateUtils.parseDateStrictly(text, DateUtils.PARSE_PATTERNS, null));
+ else
+ setValue(null);
+ }
+
+ @Override
+ public String getAsText() {
+ return DateUtils.formatDate((Date) getValue());
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/support/SimpleDecimalEditor.java b/src/main/java/com/ffii/core/support/SimpleDecimalEditor.java
new file mode 100644
index 0000000..e8e0b03
--- /dev/null
+++ b/src/main/java/com/ffii/core/support/SimpleDecimalEditor.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.support;
+
+import java.beans.PropertyEditorSupport;
+import java.math.BigDecimal;
+
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.StringUtils;
+
+/**
+ * Property Editor for BigDecimal (Defaults to null
if cannot be parsed, never throw an Exception)
+ *
+ * @see NumberUtils#toDecimal(String, BigDecimal)
+ *
+ * @author Patrick
+ */
+public class SimpleDecimalEditor extends PropertyEditorSupport {
+
+ @Override
+ public void setAsText(String value) throws IllegalArgumentException {
+ setValue(NumberUtils.toDecimal(value, null));
+ }
+
+ @Override
+ public String getAsText() {
+ return (getValue() == null ? StringUtils.EMPTY : getValue().toString());
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/support/SimpleDoubleEditor.java b/src/main/java/com/ffii/core/support/SimpleDoubleEditor.java
new file mode 100644
index 0000000..1140889
--- /dev/null
+++ b/src/main/java/com/ffii/core/support/SimpleDoubleEditor.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.support;
+
+import java.beans.PropertyEditorSupport;
+
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.StringUtils;
+
+/**
+ * Property Editor for Double (Defaults to null
if cannot be parsed, never throw an Exception)
+ *
+ * @see NumberUtils#toDouble(String, Double)
+ *
+ * @author Patrick
+ */
+public class SimpleDoubleEditor extends PropertyEditorSupport {
+
+ @Override
+ public void setAsText(String value) throws IllegalArgumentException {
+ setValue(NumberUtils.toDouble(value, null));
+ }
+
+ @Override
+ public String getAsText() {
+ return (getValue() == null ? StringUtils.EMPTY : getValue().toString());
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/support/SimpleIntegerEditor.java b/src/main/java/com/ffii/core/support/SimpleIntegerEditor.java
new file mode 100644
index 0000000..ff8ac74
--- /dev/null
+++ b/src/main/java/com/ffii/core/support/SimpleIntegerEditor.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.support;
+
+import java.beans.PropertyEditorSupport;
+
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.StringUtils;
+
+/**
+ * Property Editor for Integer (Defaults to null
if cannot be parsed, never throw an Exception)
+ *
+ * @see NumberUtils#toInt(String, Integer)
+ *
+ * @author Patrick
+ */
+public class SimpleIntegerEditor extends PropertyEditorSupport {
+
+ @Override
+ public void setAsText(String value) throws IllegalArgumentException {
+ setValue(NumberUtils.toInt(value, null));
+ }
+
+ @Override
+ public String getAsText() {
+ return (getValue() == null ? StringUtils.EMPTY : getValue().toString());
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/support/SimpleLongEditor.java b/src/main/java/com/ffii/core/support/SimpleLongEditor.java
new file mode 100644
index 0000000..e1a1c83
--- /dev/null
+++ b/src/main/java/com/ffii/core/support/SimpleLongEditor.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.support;
+
+import java.beans.PropertyEditorSupport;
+
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.StringUtils;
+
+/**
+ * Property Editor for Long (Defaults to null
if cannot be parsed, never throw an Exception)
+ *
+ * @see NumberUtils#toLong(String, Long)
+ *
+ * @author Patrick
+ */
+public class SimpleLongEditor extends PropertyEditorSupport {
+
+ @Override
+ public void setAsText(String value) throws IllegalArgumentException {
+ setValue(NumberUtils.toLong(value, null));
+ }
+
+ @Override
+ public String getAsText() {
+ return (getValue() == null ? StringUtils.EMPTY : getValue().toString());
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/support/SimpleStringTrimToEmptyEditor.java b/src/main/java/com/ffii/core/support/SimpleStringTrimToEmptyEditor.java
new file mode 100644
index 0000000..ada9880
--- /dev/null
+++ b/src/main/java/com/ffii/core/support/SimpleStringTrimToEmptyEditor.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.support;
+
+import java.beans.PropertyEditorSupport;
+
+import com.ffii.core.utils.StringUtils;
+
+/**
+ *
+ * Property Editor for String
+ *
+ *
+ * Use {@link StringUtils.trimToEmpty()} when binding the value and getting the value to display
+ *
+ *
+ * @see StringUtils#trimToEmpty(String)
+ *
+ * @author Patrick
+ */
+public class SimpleStringTrimToEmptyEditor extends PropertyEditorSupport {
+
+ @Override
+ public void setAsText(String value) throws IllegalArgumentException {
+ setValue(StringUtils.trimToEmpty(value));
+ }
+
+ @Override
+ public String getAsText() {
+ return (getValue() == null ? StringUtils.EMPTY : StringUtils.trimToEmpty((String) getValue()));
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/support/SimpleStringTrimToNullEditor.java b/src/main/java/com/ffii/core/support/SimpleStringTrimToNullEditor.java
new file mode 100644
index 0000000..26703d7
--- /dev/null
+++ b/src/main/java/com/ffii/core/support/SimpleStringTrimToNullEditor.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.support;
+
+import java.beans.PropertyEditorSupport;
+
+import com.ffii.core.utils.StringUtils;
+
+/**
+ *
+ * Property Editor for String
+ *
+ *
+ * Use {@link StringUtils.trimToNull()} when binding the value, but use {@link StringUtils.trimToEmpty()} when getting the value to display
+ *
+ *
+ * @see StringUtils#trimToNull(String)
+ * @see StringUtils#trimToEmpty(String)
+ *
+ * @author Patrick
+ */
+public class SimpleStringTrimToNullEditor extends PropertyEditorSupport {
+
+ @Override
+ public void setAsText(String value) throws IllegalArgumentException {
+ setValue(StringUtils.trimToNull(value));
+ }
+
+ @Override
+ public String getAsText() {
+ return (getValue() == null ? StringUtils.EMPTY : StringUtils.trimToEmpty((String) getValue()));
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/utils/ArgsBuilder.java b/src/main/java/com/ffii/core/utils/ArgsBuilder.java
new file mode 100644
index 0000000..7bafd71
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/ArgsBuilder.java
@@ -0,0 +1,17 @@
+package com.ffii.core.utils;
+
+import java.util.Map;
+
+/**
+ * @author fung
+ */
+public class ArgsBuilder extends MapBuilder {
+
+ public ArgsBuilder() {
+ }
+
+ public ArgsBuilder(Map args) {
+ super(args);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/utils/BooleanUtils.java b/src/main/java/com/ffii/core/utils/BooleanUtils.java
new file mode 100644
index 0000000..ea4feea
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/BooleanUtils.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+public class BooleanUtils extends org.apache.commons.lang3.BooleanUtils {
+
+ /**
+ *
+ * For the case of {@code Number}, only if the value is {@code 1} will return {@code true}. Otherwise, {@code false} is returned.
+ *
+ *
+ * For the case of {@code String}, {@code 'true'}, {@code 'on'} or {@code 'yes'} (case insensitive) will return {@code true}. Otherwise, {@code false} is
+ * returned.
+ *
+ *
+ * For the case of {@code Boolean}, only if the {@code Boolean} value is {@code true} will return {@code true}, handling {@code null} by returning
+ * {@code false}.
+ *
+ *
+ * For any other cases including {@code null} will return {@code false}.
+ *
+ */
+ public static boolean isTrue(Object obj) {
+ if (obj instanceof Number) {
+ return ((Number) obj).intValue() == 1;
+ } else if (obj instanceof String) {
+ return toBoolean((String) obj);
+ } else {
+ return Boolean.TRUE.equals(obj);
+ }
+ }
+
+ /**
+ *
+ * For the case of {@code Number}, only if the value is {@code 0} will return {@code true}. Otherwise, {@code false} is returned.
+ *
+ *
+ * For the case of {@code String}, {@code true} is returned unless the value is {@code 'true'}, {@code 'on'} or {@code 'yes'} (case insensitive) which will
+ * return {@code false}.
+ *
+ *
+ * For the case of {@code Boolean}, only if the {@code Boolean} value is {@code false} will return {@code true}, handling {@code null} by returning
+ * {@code false}.
+ *
+ *
+ * For any other cases including {@code null} will return {@code false}.
+ *
+ */
+ public static boolean isFalse(Object obj) {
+ if (obj instanceof Number) {
+ return ((Number) obj).intValue() == 0;
+ } else if (obj instanceof String) {
+ return !toBoolean((String) obj);
+ } else {
+ return Boolean.FALSE.equals(obj);
+ }
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/utils/CheckDigitUtils.java b/src/main/java/com/ffii/core/utils/CheckDigitUtils.java
new file mode 100644
index 0000000..d1880e5
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/CheckDigitUtils.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+/**
+ * CheckDigitUtils
+ *
+ * @author Patrick
+ */
+public abstract class CheckDigitUtils {
+
+ /*
+ * ISO/IEC 7812-1 Annex B
+ * Check digit calc method
+ */
+ public static int luhnCalc(String digitsString) {
+ int sum = 0;
+ boolean alternate = false;
+ for (int i = 0; i < digitsString.length(); i++) {
+ int n = Integer.parseInt(digitsString.substring(i, i + 1));
+ if (alternate) {
+ n *= 2;
+ if (n > 9) {
+ n = (n % 10) + 1;
+ }
+ }
+ sum += n;
+ alternate = !alternate;
+ }
+ return ((sum * 9) % 10);
+ }
+
+ /*
+ * ISO/IEC 7812-1 Annex B
+ * Check digit check method
+ */
+ public static boolean luhnCheck(String digitsStringWithCheckDigit) {
+ int sum = 0;
+ boolean alternate = false;
+ for (int i = digitsStringWithCheckDigit.length() - 1; i >= 0; i--) {
+ try {
+ int n = Integer.parseInt(digitsStringWithCheckDigit.substring(i, i + 1));
+ if (alternate) {
+ n *= 2;
+ if (n > 9) {
+ n = (n % 10) + 1;
+ }
+ }
+ sum += n;
+ alternate = !alternate;
+ } catch (NumberFormatException nfe) {
+ continue;
+ }
+ }
+ return (sum % 10 == 0);
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/utils/CriteriaArgsBuilder.java b/src/main/java/com/ffii/core/utils/CriteriaArgsBuilder.java
new file mode 100644
index 0000000..c5d1651
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/CriteriaArgsBuilder.java
@@ -0,0 +1,119 @@
+package com.ffii.core.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.web.bind.ServletRequestBindingException;
+
+/**
+ * ArgsBuilder of Criteria
+ *
+ * @see {@link CriteriaUtils}
+ * @author fung
+ */
+public class CriteriaArgsBuilder {
+
+ private Map args;
+ private HttpServletRequest request;
+
+ public CriteriaArgsBuilder(HttpServletRequest request) {
+ args = new HashMap();
+ this.request = request;
+ }
+
+ public CriteriaArgsBuilder(HttpServletRequest request, Map args) {
+ this.args = args;
+ this.request = request;
+ }
+
+ public CriteriaArgsBuilder addStringExact(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addStringExact(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addStringLike(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addStringLike(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addString(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addString(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addStringContains(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addStringContains(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addStringStartsWith(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addStringStartsWith(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addStringEndsWith(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addStringEndsWith(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addStringList(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addStringList(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addStringCsv(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addStringCsv(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addInteger(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addInteger(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addNonZeroInteger(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addNonZeroInteger(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addIntegerList(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addIntegerList(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addNonZeroIntegerList(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addNonZeroIntegerList(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addLong(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addLong(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addNonZeroLong(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addNonZeroLong(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addDate(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addDate(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addDateTo(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addDateTo(request, args, paramName);
+ return this;
+ }
+
+ public CriteriaArgsBuilder addBoolean(String paramName) throws ServletRequestBindingException {
+ CriteriaUtils.addBoolean(request, args, paramName);
+ return this;
+ }
+
+ public Map toMap() {
+ return this.args;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/utils/CriteriaUtils.java b/src/main/java/com/ffii/core/utils/CriteriaUtils.java
new file mode 100644
index 0000000..5d14dce
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/CriteriaUtils.java
@@ -0,0 +1,191 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.sql.Date;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.web.bind.ServletRequestBindingException;
+
+import com.ffii.core.utils.web.ServletRequestUtils;
+
+/**
+ * Utils for getting parameter values from HTTP Request and put them into a criteria map if found
+ *
+ * @author Patrick
+ */
+public abstract class CriteriaUtils {
+
+ /**
+ * Alias for {@link #addString(HttpServletRequest, Map, String)}
+ */
+ public static void addStringExact(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ addString(request, args, paramName);
+ }
+
+ /**
+ * Alias for {@link #addStringContains(HttpServletRequest, Map, String)}
+ */
+ public static void addStringLike(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ addStringContains(request, args, paramName);
+ }
+
+ /**
+ * Add a String (an exact value for using equal sign in SQL) parameter to criteria. Usually used for exact string search.
+ */
+ public static void addString(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ String value = ServletRequestUtils.getTrimmedStringParameter(request, paramName);
+ if (value != null) args.put(paramName, value);
+ }
+
+ /**
+ * Add a String (a substring value for using LIKE
in SQL) parameter to criteria. Usually used for substring search for values that contains the
+ * input value.
+ */
+ public static void addStringContains(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ String value = ServletRequestUtils.getTrimmedStringParameter(request, paramName);
+ if (value != null) args.put(paramName, StringUtils.PERCENT + value + StringUtils.PERCENT);
+ }
+
+ /**
+ * Add a String (a substring value for using LIKE
in SQL) parameter to criteria. Usually used for substring search for values that starts with
+ * the input value.
+ */
+ public static void addStringStartsWith(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ String value = ServletRequestUtils.getTrimmedStringParameter(request, paramName);
+ if (value != null) args.put(paramName, value + StringUtils.PERCENT);
+ }
+
+ /**
+ * Add a String (a substring value for using LIKE
in SQL) parameter to criteria. Usually used for substring search for values that ends with
+ * the input value.
+ */
+ public static void addStringEndsWith(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ String value = ServletRequestUtils.getTrimmedStringParameter(request, paramName);
+ if (value != null) args.put(paramName, StringUtils.PERCENT + value);
+ }
+
+ /**
+ * Add String List parameter to criteria.
+ */
+ public static void addStringList(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ String[] params = ServletRequestUtils.getStringParameters(request, paramName);
+ if (params.length > 0) {
+ List value = new ArrayList();
+ for (int i = 0; i < params.length; i++)
+ if (StringUtils.isNotBlank(params[i])) value.add(params[i]);
+ if (value.size() > 0) args.put(paramName, value);
+ }
+ }
+
+ /**
+ * Add String CSV parameter to criteria.
+ */
+ public static void addStringCsv(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ String text = ServletRequestUtils.getStringParameter(request, paramName);
+ if (StringUtils.isNotEmpty(text)) {
+ String[] params = text.split(",");
+ List values = new ArrayList();
+ for (int i = 0; i < params.length; i++)
+ values.add(params[i]);
+ args.put(paramName, values);
+ }
+ }
+
+ /**
+ * Add an Integer parameter to criteria.
+ */
+ public static void addInteger(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ Integer value = ServletRequestUtils.getIntParameter(request, paramName, null);
+ if (value != null) args.put(paramName, value);
+ }
+
+ /**
+ * Add a non-zero Integer parameter to criteria.
+ */
+ public static void addNonZeroInteger(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ Integer value = ServletRequestUtils.getIntParameter(request, paramName, null);
+ if (value != null && value.intValue() != 0) args.put(paramName, value);
+ }
+
+ /**
+ * Add Integer List parameter to criteria.
+ */
+ public static void addIntegerList(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ int[] params = ServletRequestUtils.getIntParameters(request, paramName);
+ if (params.length > 0) {
+ List values = new ArrayList();
+ for (int i = 0; i < params.length; i++)
+ values.add(params[i]);
+ args.put(paramName, values);
+ }
+ }
+
+ /**
+ * Add non-zero Integer List parameter to criteria.
+ */
+ public static void addNonZeroIntegerList(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ int[] params = ServletRequestUtils.getIntParameters(request, paramName);
+ if (params.length > 0) {
+ List values = new ArrayList();
+ for (int i = 0; i < params.length; i++)
+ if (params[i] != 0) values.add(params[i]);
+ args.put(paramName, values);
+ }
+ }
+
+ /**
+ * Add a Long parameter to criteria.
+ */
+ public static void addLong(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ Long value = ServletRequestUtils.getLongParameter(request, paramName, null);
+ if (value != null) args.put(paramName, value);
+ }
+
+ /**
+ * Add a non-zero Long parameter to criteria.
+ */
+ public static void addNonZeroLong(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ Long value = ServletRequestUtils.getLongParameter(request, paramName, null);
+ if (value != null && value.longValue() != 0L) args.put(paramName, value);
+ }
+
+ /**
+ * Add a SQL Date parameter to criteria. Usually used for FROM
date range search, or exact date search.
+ */
+ public static void addDate(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ Date value = ServletRequestUtils.getSqlDateParameter(request, paramName);
+ if (value != null) args.put(paramName, value);
+ }
+
+ /**
+ * Add a SQL Date (plus 1 day) parameter to criteria. Usually used for {@code TO} date range search.
+ */
+ public static void addDateTo(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ Date value = ServletRequestUtils.getSqlDateParameter(request, paramName);
+ if (value != null) args.put(paramName, DateUtils.add(value.getClass(), value, Calendar.DAY_OF_MONTH, 1));
+ }
+
+ /**
+ * Add a Boolean parameter to criteria if it's not {@code null}. Accepts "true", "on", "yes" (any case) and "1" as values for true; treats every other
+ * non-empty value as false (i.e. parses leniently).
+ */
+ public static void addBoolean(HttpServletRequest request, Map args, String paramName) throws ServletRequestBindingException {
+ Boolean value = ServletRequestUtils.getBooleanParameter(request, paramName);
+ if (value != null) args.put(paramName, value);
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/utils/DataBindUtils.java b/src/main/java/com/ffii/core/utils/DataBindUtils.java
new file mode 100644
index 0000000..fb76b25
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/DataBindUtils.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.util.Map;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.web.bind.WebDataBinder;
+
+import com.ffii.core.support.SimpleDateEditor;
+import com.ffii.core.support.SimpleIntegerEditor;
+import com.ffii.core.support.SimpleStringTrimToNullEditor;
+
+/**
+ * DataBindUtils
+ *
+ * @author Patrick
+ */
+public abstract class DataBindUtils {
+ /** coreFields */
+ public static final String[] coreFields = { Params.ID, "ownerId", "deleted", "createdBy", "modifiedBy", "created", "modified", "password" };
+
+ /**
+ *
+ * @param object
+ * @return WebDataBinder
+ */
+ private static WebDataBinder createBinder(Object object) {
+ WebDataBinder binder = new WebDataBinder(object);
+ binder.registerCustomEditor(String.class, new SimpleStringTrimToNullEditor());
+ binder.registerCustomEditor(Integer.class, new SimpleIntegerEditor());
+ binder.registerCustomEditor(java.util.Date.class, new SimpleDateEditor());
+ return binder;
+ }
+
+ /**
+ * Binds data using {@link WebDataBinder} with the following custom editors:-
+ *
+ *
+ * {@link SimpleStringTrimToNullEditor}
+ * {@link SimpleIntegerEditor}
+ * {@link SimpleDateEditor}
+ *
+ *
+ * @param record
+ * record Map
+ * @param object
+ * the target object to bind onto
+ * @param disallowFields
+ * optional
+ */
+ public static void bindRecord(Map record, Object object, String... disallowFields) {
+ WebDataBinder binder = createBinder(object);
+ binder.setDisallowedFields(disallowFields);
+ binder.bind(new MutablePropertyValues(record));
+ };
+
+ /**
+ * Binds data using {@link WebDataBinder} with the following custom editors:-
+ *
+ *
+ * {@link SimpleStringTrimToNullEditor}
+ * {@link SimpleIntegerEditor}
+ * {@link SimpleDateEditor}
+ *
+ *
+ *
+ * Important: The following system fields will NOT be binded for security reasons.
+ *
+ * id
+ * ownerId
+ * deleted
+ * createdBy
+ * modifiedBy
+ * created
+ * modified
+ * password
+ *
+ *
+ *
+ * @param record
+ * record Map
+ * @param object
+ * the target object to bind onto
+ * @param disallowFields
+ * optional
+ */
+ public static void bindRecordWithoutCore(Map record, Object object, String... disallowFields) {
+ WebDataBinder binder = createBinder(object);
+
+ binder.setDisallowedFields(ArrayUtils.addAll(coreFields, disallowFields));
+ binder.bind(new MutablePropertyValues(record));
+ };
+}
diff --git a/src/main/java/com/ffii/core/utils/DateUtils.java b/src/main/java/com/ffii/core/utils/DateUtils.java
new file mode 100644
index 0000000..b87376d
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/DateUtils.java
@@ -0,0 +1,1021 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.sql.Timestamp;
+import java.text.DecimalFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.time.FastDateFormat;
+
+/**
+ * Utility class for date parsing and formatting.
+ *
+ * @see FastDateFormat
+ * @see ISO 8601
+ * @see RFC 822
+ *
+ * @author Patrick
+ */
+public abstract class DateUtils extends org.apache.commons.lang3.time.DateUtils {
+
+ /** Used to format double digit hours, minutes, and seconds */
+ private static final DecimalFormat DF00 = new DecimalFormat("00");
+
+ /** RegEx pattern to test 24-hour 4-digit time format */
+ private static final Pattern PATTERN_24HR_TIME_STRING = Pattern.compile("^(([0-1][0-9])|(2[0-3]))[0-5][0-9]$");
+
+ /**
+ * HK date formatter for date without time zone. The format used is dd/MM/yyyy .
+ */
+ public static final FastDateFormat HK_DATE_FORMAT = FastDateFormat.getInstance("dd/MM/yyyy");
+
+ /**
+ * HK date formatter for datetime. The format used is dd/MM/yyyy HH:mm:ss .
+ */
+ public static final FastDateFormat HK_DATETIME_FORMAT = FastDateFormat.getInstance("dd/MM/yyyy HH:mm:ss");
+
+ /**
+ * SQL date formatter for date without time zone. The format used is yyyy-MM-dd .
+ */
+ public static final FastDateFormat SQL_DATE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd");
+
+ /**
+ * Ext JS ISO8601 formatter for date-time with time zone. The format used is yyyy-MM-dd'T'HH:mm:ssZZ .
+ */
+ public static final FastDateFormat ISO_JS_DATETIME_TIME_ZONE_FORMAT = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+
+ /**
+ * Default parse patterns
+ *
+ * dd/MM/yyyy
+ * dd/MM/yyyy HH:mm:ss
+ * yyyy-MM-dd
+ * yyyy-MM-dd'T'HH:mm:ss
+ * yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
+ * yyyy-MM-dd'T'HH:mm:ss.SSSZ
+ *
+ */
+ public static final String[] PARSE_PATTERNS = { "dd/MM/yyyy","dd/MM/yyyy HH:mm:ss", "yyyy-MM-dd", "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "yyyy-MM-dd'T'HH:mm:ss.SSSZ" };
+
+ /**
+ * PayPal parse patterns
+ *
+ * HH:mm:ss MMM dd, yyyy z
+ *
+ */
+ public static final String[] PARSE_PATTERNS_PAYPAL = { "HH:mm:ss MMM dd, yyyy z" };
+
+ /**
+ * Formats a Date as String using the default pattern
+ *
+ * @param date
+ * the date
object, may be {@code null}
+ *
+ * @return the formatted date string, or "" if date
is {@code null}
+ */
+ public static String formatDate(Date date) {
+ return formatDate(date, StringUtils.EMPTY);
+ }
+
+ /**
+ * Formats a Date as String using the default pattern, with a fallback default value if the date is null
+ *
+ * @param date
+ * the date
object, may be {@code null}
+ * @param defaultValue
+ * the value to return if date
is {@code null}
+ *
+ * @return the formatted date string, or the default value if date
is {@code null}
+ */
+ public static String formatDate(Date date, String defaultValue) {
+ if (date == null) return defaultValue;
+ return HK_DATE_FORMAT.format(date);
+ }
+
+ /**
+ * Formats a Date as String using the provided pattern, with a fallback default value if the date is null
+ *
+ * @param date
+ * the date
object, may be {@code null}
+ * @param pattern
+ * {@link java.text.SimpleDateFormat} compatible pattern
+ * @param defaultValue
+ * the value to return if date
is {@code null}
+ *
+ * @return the formatted date string, or the default value if date
is {@code null}
+ *
+ * @throws IllegalArgumentException
+ * if pattern is invalid
+ */
+ public static String formatDate(Date date, String pattern, String defaultValue) {
+ if (date == null) return defaultValue;
+ if (HK_DATE_FORMAT.getPattern().equals(pattern)) {
+ return HK_DATE_FORMAT.format(date);
+ } else if (SQL_DATE_FORMAT.getPattern().equals(pattern)) {
+ return SQL_DATE_FORMAT.format(date);
+ } else {
+ return FastDateFormat.getInstance(pattern).format(date);
+ }
+ }
+
+ /**
+ *
+ * Parses a string representing a date by trying a variety of different parsers.
+ *
+ *
+ * The parse will try each parse pattern in turn. A parse is only deemed successful if it parses the whole of the input string. If no parse patterns match,
+ * the default value is returned.
+ *
+ *
+ * The parser parses strictly - it does not allow for dates such as "February 942, 1996".
+ *
+ *
+ * @param str
+ * the date to parse, not null
+ * @param parsePatterns
+ * the date format patterns to use, see {@link SimpleDateFormat}, not null
+ * @param defaultValue
+ * the default value to use as fallback
+ *
+ * @return the parsed {@link Date} object
+ *
+ * @see DateUtils#parseDateStrictly(String, String[])
+ */
+ public static Date parseDateStrictly(String str, String[] parsePatterns, Date defaultValue) {
+ if (str == null) return defaultValue;
+ try {
+ return parseDateStrictly(str, parsePatterns);
+ } catch (ParseException e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ *
+ * Parses a string representing a date by trying a variety of different parsers.
+ *
+ *
+ * The parse will try each parse pattern in turn. A parse is only deemed successful if it parses the whole of the input string. If no parse patterns match,
+ * the default value is returned.
+ *
+ *
+ * The parser parses strictly - it does not allow for dates such as "February 942, 1996".
+ *
+ *
+ * @param str
+ * the date to parse, not null
+ * @param defaultValue
+ * the default value to use as fallback
+ *
+ * @return the parsed {@link Date} object
+ *
+ * @see DateUtils#parseDateStrictly(String, String[])
+ */
+ public static Date parseDateStrictly(String str, Date defaultValue) {
+ return parseDateStrictly(str, PARSE_PATTERNS, defaultValue);
+ }
+
+ /**
+ * Clone a date
+ *
+ * @return The cloned date, or null if input date is null
+ */
+ @SuppressWarnings("unchecked")
+ public static T clone(Date date) {
+ return (date == null) ? null : (T) date.clone();
+ }
+
+ /**
+ * Check if the time is valid 24-hour time
+ */
+ @Deprecated
+ public static boolean isValid24HourTime(int time) {
+ int hour = time / 100;
+ int minute = time - hour * 100;
+
+ // invalid hour
+ if (hour < 0 || hour > 23) return false;
+
+ // invalid minute
+ if (minute < 0 || minute > 59) return false;
+
+ return true;
+ }
+
+ /**
+ * Check if the time string is valid 24-hour format (4 digit without any symbols)
+ */
+ public static final boolean isValid24HourTime(String timeString) {
+ if (timeString == null || timeString.length() != 4) {
+ // if it's null or not 4-digit
+ return false;
+ } else {
+ // else check against RegEx
+ return PATTERN_24HR_TIME_STRING.matcher(timeString).matches();
+ }
+ }
+
+ /**
+ * Converts 24-hour time string to a long value in millisecond
+ *
+ * @param str
+ * 24-hour time string in the format of "hh:mm" or "hhmm"
+ */
+ public static long convertTimeStringToMillisecond(String str) {
+ if (StringUtils.isNotBlank(str)) {
+ str = StringUtils.remove(str, ':');
+ int time = NumberUtils.toInt(str);
+ int hour = time / 100;
+ int minute = time - hour * 100;
+ return hour * 3600000 + minute * 60000;
+ }
+ return 0L;
+ }
+
+ /**
+ * Returns a new Date object by adding the time value from a 24-hour time string
+ *
+ * @param clazz
+ * the Date class to return
+ * @param date
+ * the Date object that the time to add to, will not be changed
+ * @param str
+ * 24-hour time string in the format of "hh:mm" or "hhmm"
+ */
+ @SuppressWarnings("unchecked")
+ public static T addTimeToDate(Class clazz, Date date, String str) {
+ if (date != null && StringUtils.isNotBlank(str)) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ c.set(Calendar.HOUR_OF_DAY, 0);
+ c.set(Calendar.MINUTE, 0);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ long time = c.getTimeInMillis() + convertTimeStringToMillisecond(str);
+ if (clazz == Date.class) {
+ return (T) new Date(time);
+ } else if (clazz == java.sql.Date.class) {
+ return (T) new java.sql.Date(time);
+ } else if (clazz == Timestamp.class) {
+ return (T) new java.sql.Timestamp(time);
+ } else {
+ throw new UnsupportedOperationException(clazz.getName() + " is not supported.");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns today with only the date component
+ */
+ public static Date getToday() {
+ return truncate(new Date(), Calendar.DAY_OF_MONTH);
+ }
+
+ /**
+ * Returns the week number within the current year (e.g. if the date is within the first week of the year, it will return 1)
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ */
+ public static final int getWeekOfYear(Date date) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ return calendar.get(Calendar.WEEK_OF_YEAR);
+ }
+
+ /**
+ * Returns the week number within the current year (e.g. if the date is within the first week of the year, it will return 1), where you can specify the
+ * first day of the week
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ * @param firstDayOfWeek
+ * the first day of the week (e.g. Calendar.SUNDAY
, Calendar.MONDAY
, etc)
+ */
+ public static final int getWeekOfYear(Date date, int firstDayOfWeek) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ calendar.setFirstDayOfWeek(firstDayOfWeek);
+ return calendar.get(Calendar.WEEK_OF_YEAR);
+ }
+
+ /**
+ * Returns {@code true} if the {@code date} is earlier than today (ignoring the time component), returns {@code false} if later than or same as today
+ */
+ public static boolean isEarlierThanToday(Date date) {
+ return truncate(date, Calendar.DAY_OF_MONTH).getTime() < getToday().getTime();
+ }
+
+ /**
+ * Returns {@code true} if the {@code date} is later than today (ignoring the time component), returns {@code false} if earlier than or same as today
+ */
+ public static boolean isLaterThanToday(Date date) {
+ return truncate(date, Calendar.DAY_OF_MONTH).getTime() > getToday().getTime();
+ }
+
+ /**
+ * Returns {@code true} if date {@code a} is earlier than date {@code b} (ignoring the time component), returns {@code false} if later than or same day
+ */
+ public static boolean isEarlierThan(Date a, Date b) {
+ return truncate(a, Calendar.DAY_OF_MONTH).getTime() < truncate(b, Calendar.DAY_OF_MONTH).getTime();
+ }
+
+ /**
+ * Returns {@code true} if date {@code a} is later than date {@code b} (ignoring the time component), returns {@code false} if earlier than or same day
+ */
+ public static boolean isLaterThan(Date a, Date b) {
+ return truncate(a, Calendar.DAY_OF_MONTH).getTime() > truncate(b, Calendar.DAY_OF_MONTH).getTime();
+ }
+
+ /**
+ * Returns {@code true} if date {@code a} is the same date as date {@code b} (ignoring the time component), else returns {@code false}
+ */
+ public static boolean isSameDate(Date a, Date b) {
+ return truncate(a, Calendar.DAY_OF_MONTH).getTime() == truncate(b, Calendar.DAY_OF_MONTH).getTime();
+ }
+
+ /**
+ * Calculate the difference in days of (toDate - fromDate), ignoring the time component of both dates.
+ */
+ public static long getDiffInDays(Date fromDate, Date toDate) {
+ // ignore the time component of both dates
+ Date fromDateT = truncate(fromDate, Calendar.DAY_OF_MONTH);
+ Date toDateT = truncate(toDate, Calendar.DAY_OF_MONTH);
+
+ // calculate the difference in ms divided by MILLIS_PER_DAY, thus the difference in days
+ return (toDateT.getTime() - fromDateT.getTime()) / MILLIS_PER_DAY;
+ }
+
+ /**
+ * Calculate the difference in minutes of (toDate - fromDate), by truncating up to the minute of both dates.
+ */
+ public static final long getDiffInMins(Date fromDate, Date toDate) {
+ // truncate up to the minute of both dates
+ Date fromDateT = DateUtils.truncate(fromDate, Calendar.MINUTE);
+ Date toDateT = DateUtils.truncate(toDate, Calendar.MINUTE);
+
+ return (toDateT.getTime() - fromDateT.getTime()) / DateUtils.MILLIS_PER_MINUTE;
+ }
+
+ /**
+ * Calculate time diff in mins (same day or within 1 day)
+ */
+ public static final int getDiffInMins(String timeFrom, String timeTo) {
+ int diffMins = 0;
+
+ // defaults to "0000" if empty
+ if (StringUtils.isBlank(timeFrom)) timeFrom = "0000";
+ if (StringUtils.isBlank(timeTo)) timeTo = "0000";
+
+ if (!isValid24HourTime(timeFrom) || !isValid24HourTime(timeTo)) {
+ return diffMins;
+ }
+
+ try {
+ int timeFromHour = Integer.parseInt(timeFrom.substring(0, 2));
+ int timeFromMin = Integer.parseInt(timeFrom.substring(2));
+ int timeToHour = Integer.parseInt(timeTo.substring(0, 2));
+ int timeToMin = Integer.parseInt(timeTo.substring(2));
+
+ diffMins += 60 - timeFromMin;
+ diffMins += timeToMin;
+ diffMins += ((timeToHour - 1) - timeFromHour) * 60;
+
+ if (timeToHour < timeFromHour) {
+ // may be across a 00:00
+ diffMins += 24 * 60;
+ }
+ } catch (Exception e) {
+ }
+
+ return diffMins;
+ }
+
+ public static final String get24HourTimeFromDate(Date date) {
+ if (date == null) return StringUtils.EMPTY;
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ return DF00.format(calendar.get(Calendar.HOUR_OF_DAY)) + DF00.format(calendar.get(Calendar.MINUTE));
+ }
+
+ public static final String get24HourTimeFromDateWithColon(Date date) {
+ if (date == null) return StringUtils.EMPTY;
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ return DF00.format(calendar.get(Calendar.HOUR_OF_DAY)) + ":" + DF00.format(calendar.get(Calendar.MINUTE));
+ }
+
+ /**
+ *
+ * Adds or subtracts the specified amount of time to the given calendar field to the {@code date} returning a new object (the original {@code date} object
+ * is unchanged), based on the calendar's rules.
+ *
+ *
+ * For example, to subtract 5 days from the {@code date}, you can achieve it by calling:
+ * {@code add(date, Calendar.DAY_OF_MONTH, -5)}.
+ *
+ *
+ * @param clazz
+ * the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
+ * @param date
+ * the {@code date}, not {@code null}
+ * @param calendarField
+ * the calendar field to add to (e.g. {@code Calendar.DAY_OF_MONTH})
+ * @param amount
+ * the amount to add, may be negative
+ *
+ * @return the new {@code date} object with the amount added
+ *
+ * @throws IllegalArgumentException
+ * if the {@code date} is {@code null}
+ *
+ * @see Calendar#add(int, int)
+ */
+ @SuppressWarnings("unchecked")
+ public static T add(Class clazz, Date date, int calendarField, int amount) {
+ if (date == null) {
+ throw new IllegalArgumentException("The date must not be null");
+ }
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ c.add(calendarField, amount);
+ if (clazz == Date.class) {
+ return (T) c.getTime();
+ } else if (clazz == java.sql.Date.class) {
+ return (T) new java.sql.Date(c.getTimeInMillis());
+ } else if (clazz == Timestamp.class) {
+ return (T) new java.sql.Timestamp(c.getTimeInMillis());
+ } else {
+ throw new UnsupportedOperationException(clazz.getName() + " is not supported.");
+ }
+ }
+
+ /**
+ *
+ * Adds or subtracts the specified amount of years to the {@code date} returning a new object (the original {@code date} object is unchanged), based on the
+ * calendar's rules.
+ *
+ *
+ * @param clazz
+ * the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
+ * @param date
+ * the {@code date}, not {@code null}
+ * @param amount
+ * the amount to add, may be negative
+ *
+ * @return the new {@code date} object with the amount added
+ *
+ * @throws IllegalArgumentException
+ * if the {@code date} is {@code null}
+ *
+ * @see Calendar#add(int, int)
+ */
+ public static T addYears(Class clazz, Date date, int amount) {
+ return add(clazz, date, Calendar.YEAR, amount);
+ }
+
+ /**
+ *
+ * Adds or subtracts the specified amount of months to the {@code date} returning a new object (the original {@code date} object is unchanged), based on the
+ * calendar's rules.
+ *
+ *
+ * @param clazz
+ * the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
+ * @param date
+ * the {@code date}, not {@code null}
+ * @param amount
+ * the amount to add, may be negative
+ *
+ * @return the new {@code date} object with the amount added
+ *
+ * @throws IllegalArgumentException
+ * if the {@code date} is {@code null}
+ *
+ * @see Calendar#add(int, int)
+ */
+ public static T addMonths(Class clazz, Date date, int amount) {
+ return add(clazz, date, Calendar.MONTH, amount);
+ }
+
+ /**
+ *
+ * Adds or subtracts the specified amount of days to the {@code date} returning a new object (the original {@code date} object is unchanged), based on the
+ * calendar's rules.
+ *
+ *
+ * @param clazz
+ * the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
+ * @param date
+ * the {@code date}, not {@code null}
+ * @param amount
+ * the amount to add, may be negative
+ *
+ * @return the new {@code date} object with the amount added
+ *
+ * @throws IllegalArgumentException
+ * if the {@code date} is {@code null}
+ *
+ * @see Calendar#add(int, int)
+ */
+ public static T addDays(Class clazz, Date date, int amount) {
+ return add(clazz, date, Calendar.DAY_OF_MONTH, amount);
+ }
+
+ /**
+ *
+ * Adds or subtracts the specified amount of hours to the {@code date} returning a new object (the original {@code date} object is unchanged), based on the
+ * calendar's rules.
+ *
+ *
+ * @param clazz
+ * the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
+ * @param date
+ * the {@code date}, not {@code null}
+ * @param amount
+ * the amount to add, may be negative
+ *
+ * @return the new {@code date} object with the amount added
+ *
+ * @throws IllegalArgumentException
+ * if the {@code date} is {@code null}
+ *
+ * @see Calendar#add(int, int)
+ */
+ public static T addHours(Class clazz, Date date, int amount) {
+ return add(clazz, date, Calendar.HOUR_OF_DAY, amount);
+ }
+
+ /**
+ *
+ * Adds or subtracts the specified amount of minutes to the {@code date} returning a new object (the original {@code date} object is unchanged), based on
+ * the calendar's rules.
+ *
+ *
+ * @param clazz
+ * the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
+ * @param date
+ * the {@code date}, not {@code null}
+ * @param amount
+ * the amount to add, may be negative
+ *
+ * @return the new {@code date} object with the amount added
+ *
+ * @throws IllegalArgumentException
+ * if the {@code date} is {@code null}
+ *
+ * @see Calendar#add(int, int)
+ */
+ public static T addMinutes(Class clazz, Date date, int amount) {
+ return add(clazz, date, Calendar.MINUTE, amount);
+ }
+
+ /**
+ *
+ * Adds or subtracts the specified amount of seconds to the {@code date} returning a new object (the original {@code date} object is unchanged), based on
+ * the calendar's rules.
+ *
+ *
+ * @param clazz
+ * the date class to return (supports {@link java.util.Date}, {@link java.sql.Date}, {@link java.sql.Timestamp})
+ * @param date
+ * the {@code date}, not {@code null}
+ * @param amount
+ * the amount to add, may be negative
+ *
+ * @return the new {@code date} object with the amount added
+ *
+ * @throws IllegalArgumentException
+ * if the {@code date} is {@code null}
+ *
+ * @see Calendar#add(int, int)
+ */
+ public static T addSeconds(Class clazz, Date date, int amount) {
+ return add(clazz, date, Calendar.SECOND, amount);
+ }
+
+ /**
+ * Returns the day of the month represented by this Date object.
+ *
+ * @return a value between {@code 1} and {@code 31} representing the day of the month
+ *
+ * @see Calendar#get(Calendar.DAY_OF_MONTH)
+ */
+ public static int getDay(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ return c.get(Calendar.DAY_OF_MONTH);
+ }
+
+ /**
+ * Returns the day of the week represented by this Date object.
+ *
+ * @return a value from {@code 1} (Sunday) to {@code 7} (Saturday) representing the day of the week
+ *
+ * @see Calendar#get(Calendar.DAY_OF_WEEK)
+ */
+ public static int getDayOfWeek(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ return c.get(Calendar.DAY_OF_WEEK);
+ }
+
+ /**
+ * Returns a number representing the month represented by this Date object
+ *
+ * @return a value between {@code 0} and {@code 11} representing the month, with the value {@code 0} representing January
+ *
+ * @see Calendar#get(Calendar.MONTH)
+ */
+ public static int getMonth(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ return c.get(Calendar.MONTH);
+ }
+
+ /**
+ * Returns the year represented by this Date object
+ *
+ * @see Calendar#get(Calendar.YEAR)
+ */
+ public static int getYear(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ return c.get(Calendar.YEAR);
+ }
+
+ /**
+ * Returns the number of days of the month represented by this Date object
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ */
+ public static int getNumberOfDaysOfMonth(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ return c.getActualMaximum(Calendar.DAY_OF_MONTH);
+ }
+
+ /**
+ * Returns the number of days by range represented by this Date object
+ *
+ * @param dateFrom
+ * Date Start From
+ * @param dateTo
+ * Date End To
+ */
+ public static int getNumberOfDaysByRange(Date dateFrom, Date dateTo) {
+ dateFrom = DateUtils.truncate(dateFrom, Calendar.DAY_OF_MONTH);
+ dateTo = DateUtils.truncate(dateTo, Calendar.DAY_OF_MONTH);
+
+ Calendar fromCal = Calendar.getInstance();
+ fromCal.setTime(dateFrom);
+
+ Calendar toCal = Calendar.getInstance();
+ toCal.setTime(dateTo);
+ int count = 0;
+ while (DateUtils.isEarlierThan(fromCal.getTime(), toCal.getTime())) {
+ count++;
+ fromCal.add(Calendar.DAY_OF_MONTH, 1);
+ }
+ return count;
+ }
+
+ /**
+ * Returns the first date of the month represented by this Date object
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ */
+ public static Date getFirstDateOfMonth(Date date) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ calendar.set(Calendar.DAY_OF_MONTH, 1);
+ return calendar.getTime();
+ }
+
+ /**
+ * Returns the last date of the month represented by this Date object
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ */
+ public static Date getLastDateOfMonth(Date date) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+ calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
+ return calendar.getTime();
+ }
+
+ /**
+ * Returns the first date of the last month represented by this Date object
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ */
+ public static Date getFirstDateOfLastMonth(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ c.add(Calendar.MONTH, -1);
+ c.set(Calendar.DAY_OF_MONTH, 1);
+ return c.getTime();
+ }
+
+ /**
+ * Returns the last date of the last month represented by this Date object
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ */
+ public static Date getLastDateOfLastMonth(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ c.add(Calendar.MONTH, -1);
+ c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));
+ return c.getTime();
+ }
+
+ /**
+ * Returns the first date of the year
+ *
+ * @param year
+ * the year
+ */
+ public static Date getFirstDateOfYear(int year) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(year, Calendar.JANUARY, 1, 0, 0, 0);
+ return calendar.getTime();
+ }
+
+ /**
+ * Returns the last date of the year
+ *
+ * @param year
+ * the year
+ */
+ public static Date getLastDateOfYear(int year) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(year, Calendar.DECEMBER, 31, 0, 0, 0);
+ return calendar.getTime();
+ }
+
+ /**
+ * Returns the first date of the week represented by this Date object
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ */
+ public static Date getFirstDateOfWeek(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ c.add(Calendar.DAY_OF_MONTH, -c.get(Calendar.DAY_OF_WEEK) + 1);
+ return c.getTime();
+ }
+
+ /**
+ * Returns the first date of the week represented by this Date object, where you can specify the first day of the week
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ * @param firstDayOfWeek
+ * the first day of the week (e.g. Calendar.SUNDAY
, Calendar.MONDAY
, etc)
+ */
+ public static Date getFirstDateOfWeek(Date date, int firstDayOfWeek) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ int diff = c.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek;
+ if (diff < 0) diff += 7;
+ c.add(Calendar.DAY_OF_MONTH, -diff);
+ return c.getTime();
+ }
+
+ /**
+ * Returns the last date of the week represented by this Date object
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ */
+ public static Date getLastDateOfWeek(Date date) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ c.add(Calendar.DAY_OF_MONTH, 7 - c.get(Calendar.DAY_OF_WEEK));
+ return c.getTime();
+ }
+
+ /**
+ * Returns the last date of the week represented by this Date object, where you can specify the first day of the week
+ *
+ * @param date
+ * non-{@code null} {@code date} object
+ * @param firstDayOfWeek
+ * the first day of the week (e.g. Calendar.SUNDAY
, Calendar.MONDAY
, etc)
+ */
+ public static Date getLastDateOfWeek(Date date, int firstDayOfWeek) {
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ int diff = c.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek;
+ if (diff < 0) diff += 7;
+ c.add(Calendar.DAY_OF_MONTH, 6 - diff);
+ return c.getTime();
+ }
+
+ /**
+ * Returns the first date of month by year and month
+ *
+ * @param year
+ * the year
+ * @param month
+ * a value between {@code 1} and {@code 12} representing January to December
+ */
+ public static Date getFirstDateOfMonthByYearAndMonth(int year, int month) {
+ Calendar c = Calendar.getInstance();
+ c.set(year, month - 1, 1, 0, 0, 0);
+ return c.getTime();
+ }
+
+ /**
+ * Returns the last date of month by year and month
+ *
+ * @param year
+ * the year
+ * @param month
+ * a value between {@code 1} and {@code 12} representing January to December
+ */
+ public static Date getLastDateOfMonthByYearAndMonth(int year, int month) {
+ Calendar c = Calendar.getInstance();
+ c.set(year, month - 1, 1, 0, 0, 0);
+ c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));
+ return c.getTime();
+ }
+
+ /**
+ * Returns a new {@link java.sql.Date} object from the value of the {@link java.util.Date} object, returns {@code null} if {@code date} is null
+ *
+ * @param date
+ * {@code date} object, may be {@code null}
+ */
+ public static java.sql.Date toSqlDate(Date date) {
+ if (date == null) return null;
+ return new java.sql.Date(date.getTime());
+ }
+
+ /**
+ * Returns a new {@link java.sql.Timestamp} object from the value of the {@link java.util.Date} object, returns {@code null} if {@code date} is null
+ *
+ * @param date
+ * {@code date} object, may be {@code null}
+ */
+ public static java.sql.Timestamp toTimestamp(Date date) {
+ if (date == null) return null;
+ return new java.sql.Timestamp(date.getTime());
+ }
+
+ /**
+ * Returns the number of days in a month count by the Day of Week (Sunday to Saturday)
+ *
+ * @param year
+ * 4-digit year
+ * @param month
+ * 1 to 12 (Jan to Dec)
+ * @param weekDays
+ * Calendar.SUNDAY to Calendar.SATURDAY
+ *
+ * @return the number of days in a month count by the Day of Week
+ */
+ public static int countDaysByDayOfWeek(int year, int month, int... dayOfWeeks) {
+ Calendar c = Calendar.getInstance();
+ c.clear();
+ c.set(year, month - 1, 1); // first day of month
+
+ int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
+ int daysInMonth = c.getActualMaximum(Calendar.DAY_OF_MONTH);
+
+ int count = 0;
+ for (int day = 1; day <= daysInMonth; day++) {
+ for (int dow : dayOfWeeks) {
+ if (dayOfWeek == dow) count++;
+ }
+ dayOfWeek++;
+ if (dayOfWeek > Calendar.SATURDAY) dayOfWeek = Calendar.SUNDAY;
+ }
+ return count;
+ }
+
+ /**
+ * Returns the number of days in a month count by the Day of Week (Sunday to Saturday)
+ *
+ * @param dateFrom
+ * Date Start From
+ * @param dateTo
+ * Date End To
+ * @param weekDays
+ * Calendar.SUNDAY to Calendar.SATURDAY
+ *
+ * @return the number of days in a month count by the Day of Week
+ */
+ public static int countDaysByDayOfWeek(Date dateFrom, Date dateTo, int... dayOfWeeks) {
+ dateFrom = DateUtils.truncate(dateFrom, Calendar.DAY_OF_MONTH);
+ dateTo = DateUtils.truncate(dateTo, Calendar.DAY_OF_MONTH);
+
+ Calendar fromCal = Calendar.getInstance();
+ fromCal.setTime(dateFrom);
+
+ Calendar toCal = Calendar.getInstance();
+ toCal.setTime(dateTo);
+
+ int count = 0;
+ while (DateUtils.isEarlierThan(fromCal.getTime(), toCal.getTime())) {
+ int dayOfWeek = fromCal.get(Calendar.DAY_OF_WEEK);
+ for (int dow : dayOfWeeks) {
+ if (dayOfWeek == dow) {
+ count++;
+ break;
+ }
+ }
+ fromCal.add(Calendar.DAY_OF_MONTH, 1);
+ }
+ return count;
+ }
+
+ /**
+ * Returns {@code true} if date {@code a} is the same month and year as date {@code b} (ignoring the time component), else returns {@code false}
+ */
+ public static boolean isSameMonthYear(Date a, Date b) {
+ return getYear(a) == getYear(b) && getMonth(a) == getMonth(b);
+ }
+
+ /**
+ * Returns {@code true} if date {@code a} is the same week and year as date {@code b} (ignoring the time component), else returns {@code false}
+ */
+ public static boolean isSameWeekYear(Date a, Date b) {
+ return getYear(a) == getYear(b) && getWeekOfYear(a) == getWeekOfYear(b);
+ }
+
+ /**
+ * new Date() is deprecated
+ *
+ * @param year
+ * full year
+ * @param month
+ * 0-based
+ * @param date
+ * 1-based
+ * @return java.util.Date
+ */
+ public static Date newDate(int year, int month, int date) {
+ Calendar instance = Calendar.getInstance();
+ instance.clear();
+ instance.set(year, month, date);
+ return instance.getTime();
+ }
+
+ /**
+ * new Date() is deprecated
+ *
+ * @param year
+ * full year
+ * @param month
+ * 0-based
+ * @param date
+ * 1-based
+ * @return java.util.Date
+ */
+ public static Date newDate(int year, int month, int date, int hourOfDay, int minute) {
+ Calendar instance = Calendar.getInstance();
+ instance.clear();
+ instance.set(year, month, date, hourOfDay, minute);
+ return instance.getTime();
+ }
+
+ /**
+ * new Date() is deprecated
+ *
+ * @param year
+ * full year
+ * @param month
+ * 0-based
+ * @param date
+ * 1-based
+ * @return java.util.Date
+ */
+ public static Date newDate(int year, int month, int date, int hourOfDay, int minute, int second) {
+ Calendar instance = Calendar.getInstance();
+ instance.clear();
+ instance.set(year, month, date, hourOfDay, minute, second);
+ return instance.getTime();
+ }
+}
diff --git a/src/main/java/com/ffii/core/utils/Encoding.java b/src/main/java/com/ffii/core/utils/Encoding.java
new file mode 100644
index 0000000..c1b00ea
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/Encoding.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright 2012 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+public class Encoding {
+
+ // Supported Encoding Types
+ public static final int GB2312 = 0;
+ public static final int GBK = 1;
+ public static final int GB18030 = 2;
+
+ public static final int HZ = 3;
+ public static final int BIG5 = 4;
+ public static final int CNS11643 = 5;
+
+ public static final int UTF8 = 6;
+ public static final int UTF8T = 7;
+ public static final int UTF8S = 8;
+
+ public static final int UNICODE = 9;
+ public static final int UNICODET = 10;
+ public static final int UNICODES = 11;
+
+ public static final int ISO2022CN = 12;
+ public static final int ISO2022CN_CNS = 13;
+ public static final int ISO2022CN_GB = 14;
+
+ public static final int EUC_KR = 15;
+ public static final int CP949 = 16;
+ public static final int ISO2022KR = 17;
+ public static final int JOHAB = 18;
+ public static final int SJIS = 19;
+ public static final int EUC_JP = 20;
+ public static final int ISO2022JP = 21;
+ public static final int ASCII = 22;
+ public static final int OTHER = 23;
+ public static final int TOTALTYPES = 24;
+
+ // Names of the encodings as understood by Java
+ public static String[] javaname;
+
+ // Names of the encodings for human viewing
+ public static String[] nicename;
+
+ // Names of charsets as used in charset parameter of HTML Meta tag
+ public static String[] htmlname;
+
+ static {
+ javaname = new String[TOTALTYPES];
+ nicename = new String[TOTALTYPES];
+ htmlname = new String[TOTALTYPES];
+
+ // Assign encoding names
+ javaname[GB2312] = "GB2312";
+ javaname[GBK] = "GBK";
+ javaname[GB18030] = "GB18030";
+ javaname[HZ] = "ASCII"; // What to put here? Sun doesn't support HZ
+ javaname[ISO2022CN_GB] = "ISO2022CN_GB";
+ javaname[BIG5] = "BIG5";
+ javaname[CNS11643] = "EUC-TW";
+ javaname[ISO2022CN_CNS] = "ISO2022CN_CNS";
+ javaname[ISO2022CN] = "ISO2022CN";
+ javaname[UTF8] = "UTF8";
+ javaname[UTF8T] = "UTF8";
+ javaname[UTF8S] = "UTF8";
+ javaname[UNICODE] = "Unicode";
+ javaname[UNICODET] = "Unicode";
+ javaname[UNICODES] = "Unicode";
+ javaname[EUC_KR] = "EUC_KR";
+ javaname[CP949] = "MS949";
+ javaname[ISO2022KR] = "ISO2022KR";
+ javaname[JOHAB] = "Johab";
+ javaname[SJIS] = "SJIS";
+ javaname[EUC_JP] = "EUC_JP";
+ javaname[ISO2022JP] = "ISO2022JP";
+ javaname[ASCII] = "ASCII";
+ javaname[OTHER] = "ISO8859_1";
+
+ // Assign encoding names
+ htmlname[GB2312] = "GB2312";
+ htmlname[GBK] = "GBK";
+ htmlname[GB18030] = "GB18030";
+ htmlname[HZ] = "HZ-GB-2312";
+ htmlname[ISO2022CN_GB] = "ISO-2022-CN-EXT";
+ htmlname[BIG5] = "BIG5";
+ htmlname[CNS11643] = "EUC-TW";
+ htmlname[ISO2022CN_CNS] = "ISO-2022-CN-EXT";
+ htmlname[ISO2022CN] = "ISO-2022-CN";
+ htmlname[UTF8] = "UTF-8";
+ htmlname[UTF8T] = "UTF-8";
+ htmlname[UTF8S] = "UTF-8";
+ htmlname[UNICODE] = "UTF-16";
+ htmlname[UNICODET] = "UTF-16";
+ htmlname[UNICODES] = "UTF-16";
+ htmlname[EUC_KR] = "EUC-KR";
+ htmlname[CP949] = "x-windows-949";
+ htmlname[ISO2022KR] = "ISO-2022-KR";
+ htmlname[JOHAB] = "x-Johab";
+ htmlname[SJIS] = "Shift_JIS";
+ htmlname[EUC_JP] = "EUC-JP";
+ htmlname[ISO2022JP] = "ISO-2022-JP";
+ htmlname[ASCII] = "ASCII";
+ htmlname[OTHER] = "ISO8859-1";
+
+ // Assign Human readable names
+ nicename[GB2312] = "GB-2312";
+ nicename[GBK] = "GBK";
+ nicename[GB18030] = "GB18030";
+ nicename[HZ] = "HZ";
+ nicename[ISO2022CN_GB] = "ISO2022CN-GB";
+ nicename[BIG5] = "Big5";
+ nicename[CNS11643] = "CNS11643";
+ nicename[ISO2022CN_CNS] = "ISO2022CN-CNS";
+ nicename[ISO2022CN] = "ISO2022 CN";
+ nicename[UTF8] = "UTF-8";
+ nicename[UTF8T] = "UTF-8 (Trad)";
+ nicename[UTF8S] = "UTF-8 (Simp)";
+ nicename[UNICODE] = "Unicode";
+ nicename[UNICODET] = "Unicode (Trad)";
+ nicename[UNICODES] = "Unicode (Simp)";
+ nicename[EUC_KR] = "EUC-KR";
+ nicename[CP949] = "CP949";
+ nicename[ISO2022KR] = "ISO 2022 KR";
+ nicename[JOHAB] = "Johab";
+ nicename[SJIS] = "Shift-JIS";
+ nicename[EUC_JP] = "EUC-JP";
+ nicename[ISO2022JP] = "ISO 2022 JP";
+ nicename[ASCII] = "ASCII";
+ nicename[OTHER] = "OTHER";
+ }
+}
diff --git a/src/main/java/com/ffii/core/utils/ExcelUtils.java b/src/main/java/com/ffii/core/utils/ExcelUtils.java
new file mode 100644
index 0000000..77f493f
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/ExcelUtils.java
@@ -0,0 +1,675 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.security.GeneralSecurityException;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionMode;
+import org.apache.poi.poifs.crypt.Encryptor;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DataFormatter;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.RichTextString;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+
+/**
+ * Excel Utils (for Apache POI 3.15 to 3.17)
+ *
+ * @author Patrick
+ * @version 2018-04-06
+ */
+public abstract class ExcelUtils {
+
+ /**
+ * static A to Z char array
+ */
+ private static final char[] A2Z = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
+
+ private static final DataFormatter DATA_FORMATTER = new DataFormatter();
+
+ /** max rows limit of .xls **/
+ public static final int MAX_ROWS = 65536;
+ /** max columns limit of .xls **/
+ public static final int MAX_COLS = 256;
+
+ /**
+ * Column reference to index (0-based) map, support up to 256 columns
+ * (compatible with .xls format)
+ */
+ public static final Map COL_IDX = new HashMap(MAX_COLS, 1.0f);
+
+ static {
+ for (int columnIndex = 0; columnIndex < MAX_COLS; columnIndex++) {
+ int tempColumnCount = columnIndex;
+ StringBuilder sb = new StringBuilder(2);
+ do {
+ sb.insert(0, A2Z[tempColumnCount % 26]);
+ tempColumnCount = (tempColumnCount / 26) - 1;
+ } while (tempColumnCount >= 0);
+ COL_IDX.put(sb.toString(), Integer.valueOf(columnIndex));
+ }
+ }
+
+ /**
+ * Load XSSF workbook (xlsx file) from template source.
+ *
+ * @param url the relative path to the template source, e.g.
+ * "WEB-INF/excel/exampleReportTemplate.xlsx"
+ *
+ * @return the workbook, or null if the template file cannot be loaded
+ */
+ public static final Workbook loadXSSFWorkbookFromTemplateSource(ResourceLoader resourceLoader, String url) {
+ Resource resource = resourceLoader.getResource(url);
+ try {
+ return new XSSFWorkbook(resource.getInputStream());
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Write the workbook to byte array.
+ *
+ * @param workbook The Excel workbook (cannot be null)
+ *
+ * @return the byte[], or null if IO exception occurred
+ */
+ public static final byte[] toByteArray(Workbook workbook) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ workbook.write(baos);
+ } catch (IOException e) {
+ return null;
+ }
+ return baos.toByteArray();
+ }
+
+ /**
+ * Check if the cell exists in the given sheet, row and column.
+ *
+ * @param sheet the Sheet (cannot be null)
+ * @param rowIndex 0-based row index
+ * @param colIndex 0-based column index
+ *
+ * @return {@code true} if cell exists, else {@code false}
+ */
+ public static final boolean isCellExists(Sheet sheet, int rowIndex, int colIndex) {
+ Row row = sheet.getRow(rowIndex);
+ if (row != null) {
+ Cell cell = row.getCell(colIndex);
+ return cell != null;
+ }
+ return false;
+ }
+
+ /**
+ * Convenient method to obtain the cell in the given sheet, row and column.
+ *
+ * Creates the row and the cell if not already exist.
+ *
+ * @param sheet the Sheet (cannot be null)
+ * @param rowIndex 0-based row index
+ * @param colIndex 0-based column index
+ *
+ * @return the Cell (never null)
+ */
+ public static final Cell getCell(Sheet sheet, int rowIndex, int colIndex) {
+ Row row = sheet.getRow(rowIndex);
+ if (row == null) {
+ row = sheet.createRow(rowIndex);
+ }
+ Cell cell = row.getCell(colIndex);
+ if (cell == null) {
+ cell = row.createCell(colIndex);
+ }
+ return cell;
+ }
+
+ /**
+ * Get column index by column reference (support up to 256 columns)
+ *
+ * @param columnRef column reference such as "A", "B", "AA", "AB"...
+ *
+ * @return the column index
+ *
+ * @throws NullPointerException if column reference is invalid or the index
+ * exceeds 256
+ */
+ public static final int getColumnIndex(String columnRef) {
+ return COL_IDX.get(columnRef);
+ }
+
+ /**
+ * Get column reference by column index
+ *
+ * @param columnIndex 0-based column index
+ *
+ * @return the column reference such as "A", "B", "AA", "AB"...
+ */
+ public static final String getColumnRef(int columnIndex) {
+ StringBuilder sb = new StringBuilder();
+ int tempColumnCount = columnIndex;
+ do {
+ sb.insert(0, A2Z[tempColumnCount % 26]);
+ tempColumnCount = (tempColumnCount / 26) - 1;
+ } while (tempColumnCount >= 0);
+ return sb.toString();
+ }
+
+ /**
+ * Get the Excel Cell Ref String by columnIndex and rowIndex
+ *
+ * @param columnIndex 0-based column index
+ * @param rowIndex 0-based row index
+ */
+ public static final String getCellRefString(int columnIndex, int rowIndex) {
+ StringBuilder sb = new StringBuilder();
+ int tempColumnCount = columnIndex;
+ do {
+ sb.insert(0, A2Z[tempColumnCount % 26]);
+ tempColumnCount = (tempColumnCount / 26) - 1;
+ } while (tempColumnCount >= 0);
+ sb.append(rowIndex + 1);
+ return sb.toString();
+ }
+
+ /**
+ * Get Cell value as String
+ */
+ public static String getStringValue(Cell cell) {
+ if (cell != null && cell.getCellTypeEnum() == CellType.FORMULA) {
+ try {
+ return cell.getStringCellValue();
+ } catch (Exception e) {
+ return "";
+ }
+ }
+ return DATA_FORMATTER.formatCellValue(cell);
+ }
+
+ /**
+ * Get Cell value as BigDecimal
, with a fallback value
+ *
+ * Only support {@link Cell#CELL_TYPE_NUMERIC} and {@link Cell#CELL_TYPE_STRING}
+ *
+ * @return the BigDecimal
value, or the default value if cell is
+ * null
or cell type is {@link Cell#CELL_TYPE_BLANK}
+ */
+ public static BigDecimal getDecimalValue(Cell cell, BigDecimal defaultValue) {
+ if (cell == null || cell.getCellTypeEnum() == CellType.BLANK)
+ return defaultValue;
+ if (cell.getCellTypeEnum() == CellType.STRING) {
+ return NumberUtils.toDecimal(cell.getStringCellValue());
+ } else {
+ return BigDecimal.valueOf(cell.getNumericCellValue());
+ }
+ }
+
+ /**
+ * Get Cell value as BigDecimal
+ *
+ * Only support {@link Cell#CELL_TYPE_NUMERIC} and {@link Cell#CELL_TYPE_STRING}
+ *
+ * @return the BigDecimal
value, or BigDecimal.ZERO
if
+ * cell is null
or cell type is
+ * {@link Cell#CELL_TYPE_BLANK}
+ */
+ public static BigDecimal getDecimalValue(Cell cell) {
+ return getDecimalValue(cell, BigDecimal.ZERO);
+ }
+
+ /**
+ * Get Cell value as double
+ *
+ * Only support {@link Cell#CELL_TYPE_NUMERIC} and {@link Cell#CELL_TYPE_STRING}
+ */
+ public static double getDoubleValue(Cell cell) {
+ if (cell == null)
+ return 0.0;
+ if (cell.getCellTypeEnum() == CellType.STRING) {
+ return NumberUtils.toDouble(cell.getStringCellValue());
+ } else {
+ return cell.getNumericCellValue();
+ }
+ }
+
+ /**
+ * Get Cell value as int
(rounded half-up to the nearest integer)
+ *
+ * Only support {@link Cell#CELL_TYPE_NUMERIC} and {@link Cell#CELL_TYPE_STRING}
+ */
+ public static int getIntValue(Cell cell) {
+ return (int) NumberUtils.round(getDoubleValue(cell), 0);
+ }
+
+ /**
+ * Get Cell Integer value (truncated)
+ */
+ public static Integer getIntValue(Cell cell, Integer defaultValue) {
+ if (cell == null)
+ return defaultValue;
+ if (cell.getCellTypeEnum() == CellType.STRING) {
+ return NumberUtils.toInt(cell.getStringCellValue(), defaultValue);
+ } else {
+ return (int) cell.getNumericCellValue();
+ }
+ }
+
+ /**
+ * Get Cell Date value
+ */
+ public static Date getDateValue(Cell cell) {
+ if (cell == null)
+ return null;
+ if (cell.getCellTypeEnum() == CellType.STRING) {
+ return DateUtils.parseDateStrictly(cell.getStringCellValue(), DateUtils.PARSE_PATTERNS, null);
+ }
+ if (DateUtil.isCellDateFormatted(cell)) {
+ try {
+ return DateUtil.getJavaDate(cell.getNumericCellValue());
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Convenient method to set Cell value
+ *
+ * @param cell the Cell (cannot be null)
+ * @param value the value to set
+ */
+ public static void setCellValue(Cell cell, Object value) {
+ if (value instanceof String)
+ cell.setCellValue((String) value);
+ else if (value instanceof RichTextString)
+ cell.setCellValue((RichTextString) value);
+ else if (value instanceof Number)
+ cell.setCellValue(((Number) value).doubleValue());
+ else if (value instanceof Boolean)
+ cell.setCellValue(((Boolean) value).booleanValue());
+ else if (value instanceof Calendar)
+ cell.setCellValue((Calendar) value);
+ else if (value instanceof Date)
+ cell.setCellValue((Date) value);
+ else if (value == null)
+ cell.setCellValue("");
+ else
+ throw new IllegalArgumentException(value.getClass().toString() + " is not supported");
+ }
+
+ /**
+ * Convenient method to set Cell value by Sheet, row index, and column index
+ *
+ * @param sheet the Sheet (cannot be null)
+ * @param rowIndex 0-based row index
+ * @param colIndex 0-based column index
+ * @param value the value to set
+ */
+ public static void setCellValue(Sheet sheet, int rowIndex, int colIndex, Object value) {
+ setCellValue(getCell(sheet, rowIndex, colIndex), value);
+ }
+
+ /**
+ * Increase Row Height (if necessary, but never decrease it) by counting the no.
+ * of lines in a String value
+ *
+ * @param sheet The Excel worksheet
+ * @param row The row index (0-based)
+ * @param value The (multi-line) String value to count for the no. of
+ * lines
+ * @param heightInPoints The height (in points) for 1 line of text
+ */
+ public static void increaseRowHeight(Sheet sheet, int row, String value, int heightInPoints) {
+ int lines = StringUtils.countMatches(value, "\n") + 1; // count no. of lines
+ float newHeight = heightInPoints * lines;
+
+ Row r = sheet.getRow(row);
+ if (r == null)
+ r = sheet.createRow(row);
+
+ // increase the row height if necessary, but never decrease it
+ if (r.getHeightInPoints() < newHeight) {
+ r.setHeightInPoints(newHeight);
+ }
+ }
+
+ /**
+ * Add merged region (i.e. merge cells)
+ *
+ * @param sheet The Excel worksheet
+ * @param firstRowIdx The first row index (0-based)
+ * @param lastRowIdx The last row index (0-based)
+ * @param firstColIdx The first column index (0-based)
+ * @param lastColIdx The last column index (0-based)
+ */
+ public static void addMergedRegion(Sheet sheet, int firstRowIdx, int lastRowIdx, int firstColIdx, int lastColIdx) {
+ CellRangeAddress cellRangeAddress = new CellRangeAddress(firstRowIdx, lastRowIdx, firstColIdx, lastColIdx);
+ sheet.addMergedRegion(cellRangeAddress);
+ }
+
+ /**
+ * Copy and Insert Row
+ *
+ * @param workbook The Excel workbook
+ * @param sourceSheet The source Excel worksheet
+ * @param destinationSheet The destination Excel worksheet
+ * @param sourceRowNum The source row index (0-based) to copy from
+ * @param destinationRowNum The destination row index (0-based) to insert into
+ * (from the copied row)
+ */
+ public static void copyAndInsertRow(Workbook workbook, Sheet sourceSheet, Sheet destinationSheet, int sourceRowNum,
+ int destinationRowNum) {
+ // get the source / destination row
+ Row sourceRow = sourceSheet.getRow(sourceRowNum);
+ Row destRow = destinationSheet.getRow(destinationRowNum);
+
+ // if the row exist in destination, push down all rows by 1
+ if (destRow != null) {
+ destinationSheet.shiftRows(destinationRowNum, destinationSheet.getLastRowNum(), 1, true, false);
+ }
+ // create a new row
+ destRow = destinationSheet.createRow(destinationRowNum);
+
+ // loop through source columns to add to new row
+ for (int i = 0; i < sourceRow.getLastCellNum(); i++) {
+ // grab a copy of the old cell
+ Cell oldCell = sourceRow.getCell(i);
+
+ // if the old cell is null jump to next cell
+ if (oldCell == null)
+ continue;
+
+ // create a new cell in destination row
+ Cell newCell = destRow.createCell(i);
+
+ // apply cell style to new cell from old cell
+ newCell.setCellStyle(oldCell.getCellStyle());
+
+ // if there is a cell comment, copy
+ if (oldCell.getCellComment() != null) {
+ newCell.setCellComment(oldCell.getCellComment());
+ }
+
+ // if there is a cell hyperlink, copy
+ if (oldCell.getHyperlink() != null) {
+ newCell.setHyperlink(oldCell.getHyperlink());
+ }
+
+ // copy the cell data type
+ newCell.setCellType(oldCell.getCellTypeEnum());
+
+ // copy the cell data value
+ switch (oldCell.getCellTypeEnum()) {
+ case NUMERIC:
+ newCell.setCellValue(oldCell.getNumericCellValue());
+ break;
+ case STRING:
+ newCell.setCellValue(oldCell.getRichStringCellValue());
+ break;
+ case FORMULA:
+ newCell.setCellFormula(oldCell.getCellFormula());
+ break;
+ case BLANK:
+ newCell.setCellValue(oldCell.getStringCellValue());
+ break;
+ case BOOLEAN:
+ newCell.setCellValue(oldCell.getBooleanCellValue());
+ break;
+ case ERROR:
+ newCell.setCellErrorValue(oldCell.getErrorCellValue());
+ break;
+ default:
+ break;
+ }
+ }
+
+ // if there are any merged regions in the source row, copy to new row
+ for (int i = 0; i < sourceSheet.getNumMergedRegions(); i++) {
+ CellRangeAddress cellRangeAddress = sourceSheet.getMergedRegion(i);
+ if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) {
+ addMergedRegion(destinationSheet, destRow.getRowNum(),
+ (destRow.getRowNum() + (cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow())),
+ cellRangeAddress.getFirstColumn(), cellRangeAddress.getLastColumn());
+ }
+ }
+
+ // copy row height
+ destRow.setHeight(sourceRow.getHeight());
+ }
+
+ /**
+ * Copy and Insert Row
+ *
+ * @param workbook The Excel workbook
+ * @param sheet The Excel worksheet
+ * @param sourceRowNum The source row index (0-based) to copy from
+ * @param destinationRowNum The destination row index (0-based) to insert into
+ * (from the copied row)
+ */
+ public static void copyAndInsertRow(Workbook workbook, Sheet sheet, int sourceRowNum, int destinationRowNum) {
+ copyAndInsertRow(workbook, sheet, sheet, sourceRowNum, destinationRowNum);
+ }
+
+ /**
+ * Copy Column
+ *
+ * @param workbook The Excel workbook
+ * @param sourceSheet The source Excel worksheet
+ * @param destinationSheet The destination Excel worksheet
+ * @param rowStart The source row start index (0-based) to copy from
+ * @param rowEnd The source row end index (0-based) to copy from
+ * @param sourceColumnNum The source column index (0-based) to copy from
+ * @param destinationColumnNum The destination column index (0-based) to copy
+ * into (from the copied row)
+ */
+ public static void copyColumn(Workbook workbook, Sheet sourceSheet, Sheet destinationSheet, int rowStart,
+ int rowEnd, int sourceColumnNum, int destinationColumnNum) {
+ for (int i = rowStart; i <= rowEnd; i++) {
+ Row sourceRow = sourceSheet.getRow(i);
+ if (sourceRow == null)
+ continue;
+
+ Row destinationRow = destinationSheet.getRow(i);
+ if (destinationRow == null)
+ destinationRow = destinationSheet.createRow(i);
+
+ Cell oldCell = sourceRow.getCell(sourceColumnNum);
+ if (oldCell == null)
+ continue;
+
+ Cell newCell = destinationRow.createCell(destinationColumnNum);
+
+ newCell.setCellStyle(oldCell.getCellStyle());
+
+ if (oldCell.getCellComment() != null) {
+ newCell.setCellComment(oldCell.getCellComment());
+ }
+
+ if (oldCell.getHyperlink() != null) {
+ newCell.setHyperlink(oldCell.getHyperlink());
+ }
+
+ newCell.setCellType(oldCell.getCellTypeEnum());
+
+ switch (oldCell.getCellTypeEnum()) {
+ case NUMERIC:
+ newCell.setCellValue(oldCell.getNumericCellValue());
+ break;
+ case STRING:
+ newCell.setCellValue(oldCell.getRichStringCellValue());
+ break;
+ case FORMULA:
+ newCell.setCellFormula(oldCell.getCellFormula());
+ break;
+ case BLANK:
+ newCell.setCellValue(oldCell.getStringCellValue());
+ break;
+ case BOOLEAN:
+ newCell.setCellValue(oldCell.getBooleanCellValue());
+ break;
+ case ERROR:
+ newCell.setCellErrorValue(oldCell.getErrorCellValue());
+ break;
+ default:
+ break;
+ }
+
+ for (int ii = 0; ii < sourceSheet.getNumMergedRegions(); ii++) {
+ CellRangeAddress cellRangeAddress = sourceSheet.getMergedRegion(ii);
+ if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) {
+ addMergedRegion(destinationSheet, cellRangeAddress.getFirstRow(), cellRangeAddress.getLastRow(),
+ destinationColumnNum, (destinationColumnNum
+ + (cellRangeAddress.getLastColumn() - cellRangeAddress.getFirstColumn())));
+ }
+ }
+ }
+
+ destinationSheet.setColumnWidth(destinationColumnNum, sourceSheet.getColumnWidth(sourceColumnNum));
+ }
+
+ /**
+ * Copy Column
+ *
+ * @param workbook The Excel workbook
+ * @param sheet The Excel worksheet
+ * @param rowStart The source row start index (0-based) to copy from
+ * @param rowEnd The source row end index (0-based) to copy from
+ * @param sourceColumnNum The source column index (0-based) to copy from
+ * @param destinationColumnNum The destination column index (0-based) to copy
+ * into (from the copied row)
+ */
+ public static void copyColumn(Workbook workbook, Sheet sheet, int rowStart, int rowEnd, int sourceColumnNum,
+ int destinationColumnNum) {
+ copyColumn(workbook, sheet, sheet, rowStart, rowEnd, sourceColumnNum, destinationColumnNum);
+ }
+
+ public static void shiftColumns(Row row, int startingIndex, int shiftCount) {
+ for (int i = row.getPhysicalNumberOfCells() - 1; i >= startingIndex; i--) {
+ Cell oldCell = row.getCell(i);
+ Cell newCell = row.createCell(i + shiftCount);
+
+ // apply cell style to new cell from old cell
+ newCell.setCellStyle(oldCell.getCellStyle());
+
+ // if there is a cell comment, copy
+ if (oldCell.getCellComment() != null) {
+ newCell.setCellComment(oldCell.getCellComment());
+ }
+
+ // if there is a cell hyperlink, copy
+ if (oldCell.getHyperlink() != null) {
+ newCell.setHyperlink(oldCell.getHyperlink());
+ }
+
+ // copy the cell data type
+ newCell.setCellType(oldCell.getCellTypeEnum());
+
+ // copy the cell data value
+ switch (oldCell.getCellTypeEnum()) {
+ case NUMERIC:
+ newCell.setCellValue(oldCell.getNumericCellValue());
+ break;
+ case STRING:
+ newCell.setCellValue(oldCell.getRichStringCellValue());
+ break;
+ case FORMULA:
+ newCell.setCellFormula(oldCell.getCellFormula());
+ break;
+ case BLANK:
+ newCell.setCellValue(oldCell.getStringCellValue());
+ break;
+ case BOOLEAN:
+ newCell.setCellValue(oldCell.getBooleanCellValue());
+ break;
+ case ERROR:
+ newCell.setCellErrorValue(oldCell.getErrorCellValue());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /** handle some invalid char included ( /\*[]:? ) */
+ public static void setSheetName(Workbook workbook, Sheet sheet, String name) {
+ if (workbook != null && sheet != null && StringUtils.isNotBlank(name))
+ workbook.setSheetName(workbook.getSheetIndex(sheet), name.replaceAll("[/\\\\*\\[\\]:\\?]", "_"));
+ }
+
+ /** delete row */
+ public static void deleteRow(Sheet sheet, int rowIndex) {
+ if (sheet != null) {
+ sheet.removeRow(sheet.getRow(rowIndex));
+ if (rowIndex < sheet.getLastRowNum())
+ sheet.shiftRows(rowIndex, sheet.getLastRowNum(), -1);
+ }
+ }
+
+ public static byte[] encrypt(Workbook workbook, String password) {
+ return encrypt(toByteArray(workbook), password);
+ }
+
+ public static byte[] encrypt(byte[] bytes, String password) {
+
+ POIFSFileSystem fs = new POIFSFileSystem();
+ EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile);
+ // EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile,
+ // CipherAlgorithm.aes192, HashAlgorithm.sha384, -1, -1, null);
+
+ Encryptor enc = info.getEncryptor();
+ enc.confirmPassword(password);
+
+ // Read in an existing OOXML file and write to encrypted output stream
+ // don't forget to close the output stream otherwise the padding bytes aren't
+ // added
+ try {
+ OPCPackage opc = OPCPackage.open(new ByteArrayInputStream(bytes));
+ OutputStream os = enc.getDataStream(fs);
+ opc.save(os);
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ fs.writeFilesystem(bos);
+
+ return bos.toByteArray();
+ } catch (InvalidFormatException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (GeneralSecurityException e) {
+ e.printStackTrace();
+ }
+
+ return bytes;
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/utils/FileUtils.java b/src/main/java/com/ffii/core/utils/FileUtils.java
new file mode 100644
index 0000000..fceeab5
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/FileUtils.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright 2013 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * File Utils
+ *
+ * @author Patrick
+ */
+public abstract class FileUtils {
+
+ private static final Map MIMETYPES = new HashMap<>();
+
+ static {
+ MIMETYPES.put("pdf", "application/pdf");
+
+ MIMETYPES.put("doc", "application/msword");
+ MIMETYPES.put("dot", "application/msword");
+ MIMETYPES.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
+
+ MIMETYPES.put("xls", "application/vnd.ms-excel");
+ MIMETYPES.put("xlm", "application/vnd.ms-excel");
+ MIMETYPES.put("xla", "application/vnd.ms-excel");
+ MIMETYPES.put("xlc", "application/vnd.ms-excel");
+ MIMETYPES.put("xlt", "application/vnd.ms-excel");
+ MIMETYPES.put("xlw", "application/vnd.ms-excel");
+ MIMETYPES.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+
+ MIMETYPES.put("ppt", "application/vnd.ms-powerpoint");
+ MIMETYPES.put("pps", "application/vnd.ms-powerpoint");
+ MIMETYPES.put("pot", "application/vnd.ms-powerpoint");
+ MIMETYPES.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
+
+ MIMETYPES.put("bat", "application/x-msdownload");
+ MIMETYPES.put("com", "application/x-msdownload");
+ MIMETYPES.put("dll", "application/x-msdownload");
+ MIMETYPES.put("exe", "application/x-msdownload");
+ MIMETYPES.put("msi", "application/x-msdownload");
+
+ MIMETYPES.put("swf", "application/x-shockwave-flash");
+
+ MIMETYPES.put("7z", "application/x-7z-compressed");
+ MIMETYPES.put("rar", "application/x-rar-compressed");
+ MIMETYPES.put("zip", "application/zip");
+
+ MIMETYPES.put("js", "application/javascript");
+ MIMETYPES.put("json", "application/json");
+
+ MIMETYPES.put("mpga", "audio/mpeg");
+ MIMETYPES.put("mp2", "audio/mpeg");
+ MIMETYPES.put("mp2a", "audio/mpeg");
+ MIMETYPES.put("mp3", "audio/mpeg");
+ MIMETYPES.put("m2a", "audio/mpeg");
+ MIMETYPES.put("m3a", "audio/mpeg");
+
+ MIMETYPES.put("bmp", "image/bmp");
+ MIMETYPES.put("gif", "image/gif");
+ MIMETYPES.put("jpeg", "image/jpeg");
+ MIMETYPES.put("jpg", "image/jpeg");
+ MIMETYPES.put("jpe", "image/jpeg");
+ MIMETYPES.put("JPG", "image/jpeg");
+ MIMETYPES.put("png", "image/png");
+ MIMETYPES.put("PNG", "image/png");
+ MIMETYPES.put("tiff", "image/tiff");
+ MIMETYPES.put("tif", "image/tiff");
+
+ MIMETYPES.put("css", "text/css");
+
+ MIMETYPES.put("csv", "text/csv");
+
+ MIMETYPES.put("html", "text/html");
+ MIMETYPES.put("htm", "text/html");
+
+ MIMETYPES.put("txt", "text/plain");
+ MIMETYPES.put("text", "text/plain");
+ MIMETYPES.put("conf", "text/plain");
+ MIMETYPES.put("log", "text/plain");
+
+ MIMETYPES.put("mp4", "video/mp4");
+ MIMETYPES.put("mp4v", "video/mp4");
+ MIMETYPES.put("mpg4", "video/mp4");
+
+ MIMETYPES.put("mpeg", "video/mpeg");
+ MIMETYPES.put("mpg", "video/mpeg");
+ MIMETYPES.put("mpe", "video/mpeg");
+ MIMETYPES.put("m1v", "video/mpeg");
+ MIMETYPES.put("m2v", "video/mpeg");
+
+ MIMETYPES.put("qt", "video/quicktime");
+ MIMETYPES.put("mov", "video/quicktime");
+
+ MIMETYPES.put("wmv", "video/x-ms-wmv");
+ MIMETYPES.put("wmx", "video/x-ms-wmx");
+ MIMETYPES.put("wvx", "video/x-ms-wvx");
+ MIMETYPES.put("avi", "video/x-msvideo");
+
+ // MIMETYPES.put("xxxxx", "xxxxx");
+ }
+
+ /**
+ * Guess the mimetype from the file name extension
+ *
+ * @return The mimetype guessed from the file name extension, or {@code null} if the mimetype cannot be determined
+ */
+ public static String guessMimetype(String filename) {
+ String extension = StringUtils.substringAfterLast(filename, ".");
+ String mimetype = MIMETYPES.get(extension);
+ return mimetype != null ? mimetype : "application/octet-stream";
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/utils/ImageUtils.java b/src/main/java/com/ffii/core/utils/ImageUtils.java
new file mode 100644
index 0000000..6baa18c
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/ImageUtils.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.awt.image.BufferedImage;
+
+import org.imgscalr.Scalr;
+
+/**
+ * Image Utils
+ *
+ * @author Patrick
+ */
+public class ImageUtils {
+
+ public BufferedImage resize(BufferedImage srcImage, int targetSize) {
+ return Scalr.resize(srcImage, targetSize);
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/utils/JsonUtils.java b/src/main/java/com/ffii/core/utils/JsonUtils.java
new file mode 100644
index 0000000..dd7d8fc
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/JsonUtils.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.core.JsonParseException;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * JSON Utils
+ *
+ * @author Patrick
+ */
+public abstract class JsonUtils {
+
+ // Default mapper instance
+ private static final ObjectMapper mapper = new ObjectMapper();
+
+ /**
+ * Method that can be used to serialize any Java value as a JSON String.
+ */
+ public static String toJsonString(Object obj) {
+ try {
+ return mapper.writeValueAsString(obj);
+ } catch (JsonProcessingException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Read from JSON String.
+ *
+ * @param content JSON String content
+ * @param valueType the return type
+ */
+ public static T fromJsonString(String content, Class valueType)
+ throws JsonParseException, JsonMappingException, IOException {
+ return mapper.readValue(content, valueType);
+
+ }
+
+ public static Map fromJsonStringAsMap(String content)
+ throws JsonParseException, JsonMappingException, IOException {
+ return mapper.readValue(content, new TypeReference>() {
+ });
+ }
+
+ public static List> fromJsonStringAsListOfMap(String content)
+ throws JsonParseException, JsonMappingException, IOException {
+ return mapper.readValue(content, new TypeReference>>() {
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/utils/ListUtils.java b/src/main/java/com/ffii/core/utils/ListUtils.java
new file mode 100644
index 0000000..0926c41
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/ListUtils.java
@@ -0,0 +1,26 @@
+package com.ffii.core.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * ListUtils
+ *
+ */
+public class ListUtils {
+
+ /**
+ * Convert values to ArrayList
+ *
+ * @param values
+ *
+ * @return List
+ */
+ @SuppressWarnings("unchecked")
+ public static List toArrayList(V... values) {
+ List list = new ArrayList(values.length);
+ for (int i = 0; i < values.length; i++)
+ list.add(values[i]);
+ return list;
+ }
+}
diff --git a/src/main/java/com/ffii/core/utils/LocaleUtils.java b/src/main/java/com/ffii/core/utils/LocaleUtils.java
new file mode 100644
index 0000000..124cc76
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/LocaleUtils.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.util.Locale;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.util.StringUtils;
+import org.springframework.web.servlet.LocaleResolver;
+import org.springframework.web.servlet.i18n.SessionLocaleResolver;
+
+/**
+ * Utility class for performing translation between Locale and String representations.
+ */
+public abstract class LocaleUtils {
+
+ private static final LocaleResolver localeResolver = new SessionLocaleResolver();
+
+ public static final String toString(Locale locale) {
+ return locale.toString();
+ }
+
+ public static final Locale toLocale(String localeString) {
+ return StringUtils.parseLocaleString(localeString);
+ }
+
+ public static final Locale resolveLocale(HttpServletRequest request) {
+ return localeResolver.resolveLocale(request);
+ }
+
+ public static final String resolveLocaleString(HttpServletRequest request) {
+ return resolveLocale(request).toString();
+ }
+
+ public static final void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
+ localeResolver.setLocale(request, response, locale);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/utils/MapBuilder.java b/src/main/java/com/ffii/core/utils/MapBuilder.java
new file mode 100644
index 0000000..89cda94
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/MapBuilder.java
@@ -0,0 +1,34 @@
+package com.ffii.core.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author fung
+ */
+public class MapBuilder {
+
+ private Map args;
+
+ public MapBuilder() {
+ args = new HashMap();
+ }
+
+ public MapBuilder(Map args) {
+ this.args = args;
+ }
+
+ public MapBuilder add(K key, V value) {
+ args.put(key, value);
+ return this;
+ }
+
+ public Map toMap() {
+ return args;
+ }
+
+ @Override
+ public String toString() {
+ return args.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/utils/MapUtils.java b/src/main/java/com/ffii/core/utils/MapUtils.java
new file mode 100644
index 0000000..7928c1e
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/MapUtils.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * MapUtils
+ *
+ * @author Patrick
+ */
+public class MapUtils {
+
+ /**
+ * Convert key-value pairs to HashMap
+ *
+ * @param keyValuePairs
+ * Keys and values must be in pairs
+ *
+ * @return Map
+ */
+ @SuppressWarnings("unchecked")
+ public static Map toHashMap(Object... keyValuePairs) {
+ if (keyValuePairs.length % 2 != 0)
+ throw new IllegalArgumentException("Keys and values must be in pairs");
+
+ Map map = new HashMap(keyValuePairs.length / 2);
+
+ for (int i = 0; i < keyValuePairs.length; i += 2) {
+ map.put((K) keyValuePairs[i], (V) keyValuePairs[i + 1]);
+ }
+
+ return map;
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/utils/NumberUtils.java b/src/main/java/com/ffii/core/utils/NumberUtils.java
new file mode 100644
index 0000000..278f6fc
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/NumberUtils.java
@@ -0,0 +1,471 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * NumberUtils extends from Apache Commons, and incl some methods from MathUtils
+ *
+ * @author Patrick
+ */
+public abstract class NumberUtils extends org.apache.commons.lang3.math.NumberUtils {
+
+ /**
+ * Round the given value to the specified number of decimal places. The value is rounded using the given method which is any method defined in
+ * {@link BigDecimal}.
+ *
+ * @param x
+ * the value to round
+ * @param scale
+ * the number of digits to the right of the decimal point
+ * @param roundingMethod
+ * the rounding method as defined in {@link RoundingMode}
+ * @return the rounded value
+ */
+ public static double round(double x, int scale, RoundingMode roundingMethod) {
+ try {
+ return BigDecimal.valueOf(x).setScale(scale, roundingMethod).doubleValue();
+ } catch (NumberFormatException ex) {
+ if (Double.isInfinite(x)) {
+ return x;
+ } else {
+ return Double.NaN;
+ }
+ }
+ }
+
+ /**
+ * Round the given value to the specified number of decimal places. The value is rounded using the {@link RoundingMode#HALF_UP} method.
+ *
+ * @param x
+ * the value to round
+ * @param scale
+ * the number of digits to the right of the decimal point
+ * @return the rounded value
+ * @see org.apache.commons.math.util.MathUtils#round(double, int)
+ */
+ public static double round(double x, int scale) {
+ return round(x, scale, RoundingMode.HALF_UP);
+ }
+
+ /**
+ * Round up the given value to the specified number of decimal places. The value is rounded up using the {@link RoundingMode#UP} method.
+ *
+ * @param x
+ * the value to round up
+ * @param scale
+ * the number of digits to the right of the decimal point
+ * @return the rounded up value
+ */
+ public static double roundUp(double x, int scale) {
+ return round(x, scale, RoundingMode.UP);
+ }
+
+ /**
+ * Round down the given value to the specified number of decimal places. The value is rounded down using the {@link RoundingMode#DOWN} method.
+ *
+ * @param x
+ * the value to round down
+ * @param scale
+ * the number of digits to the right of the decimal point
+ * @return the rounded down value
+ */
+ public static double roundDown(double x, int scale) {
+ return round(x, scale, RoundingMode.DOWN);
+ }
+
+ /**
+ * Return the {@code int} value of an {@code Object}, or the default value if the object is either {@code null} or not an instance of {@code Number}.
+ *
+ * @param obj
+ * the {@code Object}
+ * @param defaultValue
+ * the default value
+ * @return the {@code int} value of the {@code Object}, or the default if the object is either {@code null} or not an instance of {@code Number}
+ * @see Number#intValue()
+ */
+ public static int intValue(Object obj, int defaultValue) {
+ if (obj instanceof Number) {
+ return ((Number) obj).intValue();
+ } else if (obj instanceof String) {
+ try {
+ return Integer.parseInt((String) obj);
+ } catch (NumberFormatException nfe) {
+ }
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Return the {@code int} value of an {@code Object}, or {@code zero} if the object is either {@code null} or not an instance of {@code Number}.
+ *
+ * @param obj
+ * the {@code Object}
+ * @return the {@code int} value of the {@code Object}, or {@code zero} if the object is either {@code null} or not an instance of {@code Number}
+ * @see Number#intValue()
+ */
+ public static int intValue(Object obj) {
+ return intValue(obj, 0);
+ }
+
+ /**
+ * Convert an {@code Object} to an {@code Integer} (only if the object is an instance of {@code Number} or {@code String}), returning a default value if the
+ * conversion fails.
+ *
+ * If the object is {@code null}, the default value is returned.
+ *
+ * @param obj
+ * the object to convert, may be {@code null}
+ * @param defaultValue
+ * the default value, may be {@code null}
+ * @return the Integer represented by the object, or the default if conversion fails
+ */
+ public static Integer toInt(Object obj, Integer defaultValue) {
+ if (obj instanceof Number)
+ return Integer.valueOf(((Number) obj).intValue());
+ else if (obj instanceof String)
+ try {
+ return Integer.valueOf((String) obj);
+ } catch (NumberFormatException nfe) {
+ return defaultValue;
+ }
+ else
+ return defaultValue;
+ }
+
+ /**
+ * Return the {@code long} value of a {@code Long} object, or a default value if the object is {@code null}.
+ *
+ * @param obj
+ * the object (can be {@code null})
+ * @param defaultValue
+ * the default value
+ * @return the {@code long} value of the object if it's a number, or the default value if the object is {@code null} or {@code NaN}
+ * @see Long#longValue()
+ */
+ public static long longValue(Object obj, long defaultValue) {
+ return (obj instanceof Number) ? ((Number) obj).longValue() : defaultValue;
+ }
+
+ /**
+ * Return the {@code long} value of a {@code Long} object, or {@code zero} if the object is {@code null}.
+ *
+ * @param obj
+ * the object (can be {@code null})
+ * @return the {@code long} value of the object if it's a number, or {@code zero} if the object is {@code null} or {@code NaN}
+ * @see Long#longValue()
+ */
+ public static long longValue(Object obj) {
+ return longValue(obj, 0l);
+ }
+
+ /**
+ * Convert an {@code Object} to a {@code Long} (only if the object is an instance of {@code Number} or {@code String}), returning a default value if the
+ * conversion fails.
+ *
+ * If the object is {@code null}, the default value is returned.
+ *
+ * @param obj
+ * the object to convert, may be {@code null}
+ * @param defaultValue
+ * the default value, may be {@code null}
+ * @return the Long represented by the object, or the default if conversion fails
+ */
+ public static Long toLong(Object obj, Long defaultValue) {
+ if (obj instanceof Number)
+ return Long.valueOf(((Number) obj).longValue());
+ else if (obj instanceof String)
+ try {
+ return Long.valueOf((String) obj);
+ } catch (NumberFormatException nfe) {
+ return defaultValue;
+ }
+ else
+ return defaultValue;
+ }
+
+ /**
+ * @param obj
+ * the object (can be {@code null})
+ * @param defaultValue
+ * the default value
+ * @return the {@code double} value of the object if it's a number, or the default value if the object is {@code null} or {@code NaN}
+ * @see Double#doubleValue()
+ */
+ public static double doubleValue(Object obj, double defaultValue) {
+ return (obj instanceof Number) ? ((Number) obj).doubleValue() : defaultValue;
+ }
+
+ /**
+ * @param obj
+ * the object (can be {@code null})
+ * @param defaultValue
+ * the default value
+ * @return the {@code double} value of the object if it's a number, or {@code zero} if the object is {@code null} or {@code NaN}
+ * @see Double#doubleValue()
+ */
+ public static double doubleValue(Object obj) {
+ return doubleValue(obj, 0.0d);
+ }
+
+ /**
+ * Convert an {@code Object} to a {@code Double} (only if the object is an instance of {@code Number} or {@code String}), returning a default value if the
+ * conversion fails.
+ *
+ * If the object is {@code null}, the default value is returned.
+ *
+ * @param obj
+ * the object to convert, may be {@code null}
+ * @param defaultValue
+ * the default value, may be {@code null}
+ * @return the Double represented by the object, or the default if conversion fails
+ */
+ public static Double toDouble(Object obj, Double defaultValue) {
+ if (obj instanceof Number)
+ return Double.valueOf(((Number) obj).doubleValue());
+ else if (obj instanceof String)
+ try {
+ return Double.valueOf((String) obj);
+ } catch (NumberFormatException nfe) {
+ return defaultValue;
+ }
+ else
+ return defaultValue;
+ }
+
+ /**
+ * Return the {@code BigDecimal} object, or {@code zero} if the object is {@code null}.
+ *
+ * @param obj
+ * the {@code BigDecimal} object
+ * @return the {@code BigDecimal} object, or {@code zero} if the object is {@code null}
+ */
+ public static BigDecimal decimalValue(BigDecimal obj) {
+ return decimalValue(obj, BigDecimal.ZERO);
+ }
+
+ /**
+ * Return the {@code BigDecimal} object, or a default value if the object is {@code null}.
+ *
+ * @param obj
+ * the {@code BigDecimal} object
+ * @param defaultValue
+ * the default value
+ * @return the {@code BigDecimal} object, or the default if the object is {@code null}
+ */
+ public static BigDecimal decimalValue(BigDecimal obj, BigDecimal defaultValue) {
+ return obj == null ? defaultValue : obj;
+ }
+
+ /**
+ * Convert an {@code Object} to a {@code BigDecimal}, returning {@code BigDecimal.ZERO} if the conversion fails (e.g. the object is not an instance of
+ * {@code Number} nor {@code String}).
+ *
+ * If the object is {@code null}, {@code BigDecimal.ZERO} is returned.
+ *
+ * @param obj
+ * the object to convert, may be {@code null}
+ * @return the BigDecimal represented by the object, or {@code BigDecimal.ZERO} if conversion fails
+ */
+ public static BigDecimal toDecimal(Object obj) {
+ return toDecimal(obj, BigDecimal.ZERO);
+ }
+
+ /**
+ * Convert an {@code Object} to a {@code BigDecimal}, returning a default value if the conversion fails (e.g. the object is not an instance of
+ * {@code Number} nor {@code String}).
+ *
+ * If the object is {@code null}, the default value is returned.
+ *
+ * @param obj
+ * the object to convert, may be {@code null}
+ * @param defaultValue
+ * the default value, may be {@code null}
+ * @return the BigDecimal represented by the object, or the default if conversion fails
+ */
+ public static BigDecimal toDecimal(Object obj, BigDecimal defaultValue) {
+ if (obj instanceof BigDecimal)
+ return (BigDecimal) obj;
+ else if (obj instanceof Number)
+ return BigDecimal.valueOf(((Number) obj).doubleValue());
+ else if (obj instanceof String)
+ try {
+ return new BigDecimal((String) obj);
+ } catch (NumberFormatException nfe) {
+ return defaultValue;
+ }
+ else
+ return defaultValue;
+ }
+
+ /**
+ * Null-safe method to check if the two {@code Integer} objects have the same value.
+ *
+ *
+ * Returns {@code true} if {@code a} and {@code b} are both {@code null}.
+ * Returns {@code false} if only one of them is {@code null}.
+ * Returns {@code true} if {@code a} and {@code b} are not {@code null} and have the same {@code int} value, else returns {@code false}.
+ *
+ *
+ * @param a
+ * Integer obj, may be {@code null}
+ * @param b
+ * Integer obj, may be {@code null}
+ */
+ public static boolean isEqual(Integer a, Integer b) {
+ return a == null ? (b == null ? true : false) : (b == null ? false : a.equals(b));
+ }
+
+ /**
+ * Null-safe method to check if the two {@code Integer} objects have different values.
+ *
+ *
+ * Returns {@code false} if {@code a} and {@code b} are both {@code null}.
+ * Returns {@code true} if only one of them is {@code null}.
+ * Returns {@code true} if {@code a} and {@code b} are not {@code null} and have different {@code int} values, else returns {@code false}.
+ *
+ *
+ * @param a
+ * Integer obj, may be {@code null}
+ * @param b
+ * Integer obj, may be {@code null}
+ */
+ public static boolean isNotEqual(Integer a, Integer b) {
+ return !isEqual(a, b);
+ }
+
+ /**
+ * Null-safe method to check if the two {@code BigDecimal} objects have the same value.
+ *
+ * Two {@code BigDecimal} objects that are equal in value but have a different scale (like 2.0 and 2.00) are considered equal by this method.
+ *
+ *
+ * Returns {@code true} if {@code a} and {@code b} are both {@code null}.
+ * Returns {@code false} if only one of them is {@code null}.
+ * Returns {@code true} if {@code a} and {@code b} are not {@code null} and have the same {@code decimal} value, else returns {@code false}.
+ *
+ *
+ * @param a
+ * BigDecimal obj, may be {@code null}
+ * @param b
+ * BigDecimal obj, may be {@code null}
+ */
+ public static boolean isEqual(BigDecimal a, BigDecimal b) {
+ return a == null ? (b == null ? true : false) : (b == null ? false : a.compareTo(b) == 0);
+ }
+
+ /**
+ * Null-safe method to check if the two {@code BigDecimal} objects have different values.
+ *
+ * Two {@code BigDecimal} objects that are equal in value but have a different scale (like 2.0 and 2.00) are considered equal by this method.
+ *
+ *
+ * Returns {@code false} if {@code a} and {@code b} are both {@code null}.
+ * Returns {@code true} if only one of them is {@code null}.
+ * Returns {@code true} if {@code a} and {@code b} are not {@code null} and have different {@code decimal} values, else returns {@code false}.
+ *
+ *
+ * @param a
+ * BigDecimal obj, may be {@code null}
+ * @param b
+ * BigDecimal obj, may be {@code null}
+ */
+ public static boolean isNotEqual(BigDecimal a, BigDecimal b) {
+ return !isEqual(a, b);
+ }
+
+ /**
+ * Check if {@code BigDecimal} object {@code a} is greater than {@code BigDecimal} object {@code b}.
+ *
+ * @param a
+ * non-{@code null} BigDecimal obj
+ * @param b
+ * non-{@code null} BigDecimal obj
+ */
+ public static boolean isGreaterThan(BigDecimal a, BigDecimal b) {
+ return a.compareTo(b) > 0;
+ }
+
+ /**
+ * Check if {@code BigDecimal} object {@code a} is greater than or equals to {@code BigDecimal} object {@code b}.
+ *
+ * @param a
+ * non-{@code null} BigDecimal obj
+ * @param b
+ * non-{@code null} BigDecimal obj
+ */
+ public static boolean isGreaterThanOrEqual(BigDecimal a, BigDecimal b) {
+ return a.compareTo(b) >= 0;
+ }
+
+ /**
+ * Check if {@code BigDecimal} object {@code a} is less than {@code BigDecimal} object {@code b}.
+ *
+ * @param a
+ * non-{@code null} BigDecimal obj
+ * @param b
+ * non-{@code null} BigDecimal obj
+ */
+ public static boolean isLessThan(BigDecimal a, BigDecimal b) {
+ return a.compareTo(b) < 0;
+ }
+
+ /**
+ * Check if {@code BigDecimal} object {@code a} is less than or equals to {@code BigDecimal} object {@code b}.
+ *
+ * @param a
+ * non-{@code null} BigDecimal obj
+ * @param b
+ * non-{@code null} BigDecimal obj
+ */
+ public static boolean isLessThanOrEqual(BigDecimal a, BigDecimal b) {
+ return a.compareTo(b) <= 0;
+ }
+
+ /**
+ *
+ *
+ * NumberUtils.equalsAny(null, (Integer[]) null) = false
+ * NumberUtils.equalsAny(null, null, null) = true
+ * NumberUtils.equalsAny(null, 1, 2) = false
+ * NumberUtils.equalsAny(1, null, 2) = false
+ * NumberUtils.equalsAny(1, 1, 2) = true
+ *
+ *
+ * @param int
+ * to compare, may be {@code null}.
+ * @param searchInts
+ * a int, may be {@code null}.
+ * @return {@code true} if the num is equal to any other element of searchInts
; {@code false} if searchInts
is null or contains no
+ * matches.
+ */
+ public static boolean equalsAny(final int num, int... searchInts) {
+ if (ArrayUtils.isNotEmpty(searchInts)) {
+ for (int next : searchInts) {
+ if (num == next) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static double sum(double... nums) {
+ BigDecimal rs = BigDecimal.ZERO;
+ for (double num : nums)
+ rs = rs.add(BigDecimal.valueOf(num));
+ return rs.doubleValue();
+ }
+}
diff --git a/src/main/java/com/ffii/core/utils/Params.java b/src/main/java/com/ffii/core/utils/Params.java
new file mode 100644
index 0000000..508d890
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/Params.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+/**
+ * Static strings for standard params
+ *
+ * @author Patrick
+ */
+public abstract class Params {
+
+ public static final String ID = "id";
+
+ public static final String TYPE = "type";
+
+ public static final String QUERY = "query";
+
+ public static final String CODE = "code";
+ public static final String NAME = "name";
+ public static final String TITLE = "title";
+
+ public static final String KEY = "key";
+ public static final String VALUE = "value";
+
+ public static final String SUCCESS = "success";
+
+ public static final String AUTH = "auth";
+
+ /** Short for "message" */
+ public static final String MSG = "msg";
+
+ public static final String DETAILS = "details";
+
+ public static final String DATA = "data";
+ public static final String RECORDS = "records";
+ public static final String ROOT = "root";
+ public static final String NODE = "node";
+ public static final String EXPANDED = "expanded";
+
+ public static final String FROM = "from";
+ public static final String TO = "to";
+
+ public static final String START = "start";
+ public static final String LIMIT = "limit";
+
+ public static final String PREFIX = "prefix";
+ public static final String SUFFIX = "suffix";
+
+ public static final String LENGTH = "length";
+
+ public static final String MODE = "mode";
+
+ public static final String COUNT = "count";
+
+ public static final String TOTAL = "total";
+
+ public static final String STATUS = "status";
+
+ public static final String VERSION_ID = "versionId";
+
+ public static final String REF_ID = "refId";
+ public static final String REF_TYPE = "refType";
+ public static final String REF_CODE = "refCode";
+
+ public static final String FROM_DATE = "fromDate";
+ public static final String TO_DATE = "toDate";
+
+ public static final String MAX_ROWS = "maxRows";
+
+ public static final String METHOD_GET = "GET";
+ public static final String METHOD_POST = "POST";
+
+}
diff --git a/src/main/java/com/ffii/core/utils/SecurityUtils.java b/src/main/java/com/ffii/core/utils/SecurityUtils.java
new file mode 100644
index 0000000..add01f5
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/SecurityUtils.java
@@ -0,0 +1,225 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.regex.Pattern;
+
+import com.ffii.core.User;
+import com.ffii.tbms.user.service.UserService;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+/**
+ * Security Utils - for Spring Security
+ *
+ * @author Patrick
+ */
+public class SecurityUtils {
+
+ private static final Pattern PATTERN_DIGITS = Pattern.compile("[0-9]");
+ private static final Pattern PATTERN_A2Z_LOWER = Pattern.compile("[a-z]");
+ private static final Pattern PATTERN_A2Z_UPPER = Pattern.compile("[A-Z]");
+
+ private static final String A2Z_LOWER = "abcdefghijklmnopqrstuvwxyz";
+ private static final String A2Z_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ private static final String DIGITS = "0123456789";
+
+ /*
+ * Ref: https://www.owasp.org/index.php/Password_special_characters
+ * without space character
+ */
+ private static final String SPECIAL_CHARS = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
+ private static Pattern PATTERN_SPECIAL_CHARS = Pattern.compile("[!\"#$%&'()*+,-./:;<=>?@\\[\\\\\\]^_`{|}~]");
+
+ /**
+ * Obtains the current {@code SecurityContext}.
+ *
+ * @return the security context (never {@code null})
+ */
+ public static final SecurityContext getSecurityContext() {
+ return SecurityContextHolder.getContext();
+ }
+
+ /**
+ * @return the authenticated {@code Principal} ({@code User})
+ * @see Authentication#getPrincipal()
+ */
+ public static final User getUser() {
+ try {
+ return (User) getSecurityContext().getAuthentication().getPrincipal();
+ } catch (ClassCastException e) {
+ // no authenticated principal
+ return null;
+ } catch (NullPointerException e) {
+ // no authentication information is available
+ return null;
+ }
+ }
+
+ /**
+ * Updates the Authentication Token with the user (e.g. user changed the password)
+ *
+ * @see SecurityContext#setAuthentication(Authentication)
+ */
+ public static final void updateUserAuthentication(final User user) {
+ getSecurityContext().setAuthentication(new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities()));
+ }
+
+ /**
+ * Checks if the current user is GRANTED the {@code role}
+ *
+ * @param role
+ * the {@code role} to check for
+ * @return {@code true} if the current user is GRANTED the {@code role}, else {@code false}
+ */
+ public static final boolean isGranted(String role) {
+ Authentication authentication = getSecurityContext().getAuthentication();
+ if (authentication == null) return false;
+ for (GrantedAuthority auth : authentication.getAuthorities()) {
+ if (auth.getAuthority().equals("SUPERUSER")) return true;
+ if (role.equals(auth.getAuthority())) return true;
+ }
+ return false;
+ }
+
+ public static final boolean isGrantedOnly(String role) {
+ Authentication authentication = getSecurityContext().getAuthentication();
+ if (authentication == null) return false;
+ for (GrantedAuthority auth : authentication.getAuthorities()) {
+ if (role.equals(auth.getAuthority())) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the current user is NOT GRANTED the {@code role}
+ *
+ * @param role
+ * the {@code role} to check for
+ * @return {@code true} if the current user is NOT GRANTED the {@code role}, else {@code false}
+ */
+ public static final boolean isNotGranted(String role) {
+ return !isGranted(role);
+ }
+
+ /**
+ * Checks if the current user is GRANTED ANY of the {@code role}s
+ *
+ * @param roles
+ * the {@code role}s to check for
+ * @return {@code true} if the current user is GRANTED ANY of the {@code role}s, else {@code false}
+ */
+ public static final boolean isGrantedAny(String... roles) {
+ for (int i = 0; i < roles.length; i++) {
+ if (isGranted(roles[i])) return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the current user is NOT GRANTED ANY of the {@code role}s
+ *
+ * @param roles
+ * the {@code role}s to check for
+ * @return {@code true} if the current user is NOT GRANTED ANY of the {@code role}s, else {@code false}
+ */
+ public static final boolean isNotGrantedAny(String... roles) {
+ return !isGrantedAny(roles);
+ }
+
+ /**
+ * Checks if the current user is GRANTED ALL of the {@code role}s
+ *
+ * @param roles
+ * the {@code role}s to check for
+ * @return {@code true} if the current user is GRANTED ALL of the {@code role}s, else {@code false}
+ */
+ public static final boolean isGrantedAll(String... roles) {
+ for (int i = 0; i < roles.length; i++) {
+ if (isNotGranted(roles[i])) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Login a user non-interactively
+ *
+ * @param userService
+ * any implementation of {@link UserDetailsService}
+ * @param username
+ * the username
+ *
+ * @throws UsernameNotFoundException
+ * if the user could not be found or the user has no GrantedAuthority
+ * @throws DataAccessException
+ * if user could not be found for a repository-specific reason
+ */
+ public static final void loginUser(UserDetailsService userService, String username) {
+ /* load the user, throw exception if user not found */
+ UserDetails userDetails = userService.loadUserByUsername(username);
+
+ /* create authentication token for the specified user */
+ Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
+ getSecurityContext().setAuthentication(authentication);
+ }
+
+ public static final boolean checkPwd(String pwd, int min, int max, boolean num, boolean upperEng, boolean lowerEng, boolean special) {
+ if (pwd == null) return false;
+ if (pwd.length() < min) return false;
+ if (pwd.length() > max) return false;
+
+ if (num && !PATTERN_DIGITS.matcher(pwd).find()) return false;
+ if (upperEng && !PATTERN_A2Z_UPPER.matcher(pwd).find()) return false;
+ if (lowerEng && !PATTERN_A2Z_LOWER.matcher(pwd).find()) return false;
+ if (special && !PATTERN_SPECIAL_CHARS.matcher(pwd).find()) return false;
+
+ return true;
+ }
+
+ public static String genPwd(int length, boolean num, boolean upperEng, boolean lowerEng, boolean special) {
+ if (length <= 0) return "";
+
+ StringBuilder password = new StringBuilder(length);
+ Random random = new Random(System.nanoTime());
+
+ List charCategories = new ArrayList<>(4);
+ if (lowerEng) charCategories.add(A2Z_LOWER);
+ if (upperEng) charCategories.add(A2Z_UPPER);
+ if (num) charCategories.add(DIGITS);
+ if (special) charCategories.add(SPECIAL_CHARS);
+
+ for (int i = 0; i < length; i++) {
+ String charCategory = charCategories.get(i % charCategories.size());
+ char randomChar = charCategory.charAt(random.nextInt(charCategory.length()));
+ if (password.length() > 0)
+ password.insert(random.nextInt(password.length()), randomChar);
+ else
+ password.append(randomChar);
+ }
+
+ return password.toString();
+ }
+
+ public static void authArgs(Map args){
+ args.put("sysGroupId", getUser().getSysGroupId());
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/utils/SqlUtils.java b/src/main/java/com/ffii/core/utils/SqlUtils.java
new file mode 100644
index 0000000..8e04ca5
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/SqlUtils.java
@@ -0,0 +1,20 @@
+package com.ffii.core.utils;
+
+public class SqlUtils {
+
+ /**
+ * for IN() of SQL. if no parameter, it will return "''"
+ *
+ * @return 'obj1','obj2','obj3','obj4'...
+ */
+ public static String toInString(Object... objs) {
+ String rs = "";
+ for (int i = 0; i < objs.length; i++) {
+ if (i != 0) rs += ",";
+ rs += "'" + objs[i] + "'";
+ }
+ if (StringUtils.isBlank(rs))
+ rs += "''";
+ return rs;
+ }
+}
diff --git a/src/main/java/com/ffii/core/utils/StringUtils.java b/src/main/java/com/ffii/core/utils/StringUtils.java
new file mode 100644
index 0000000..7c95bf8
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/StringUtils.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+/**
+ * String Utils based on Apache Commons StringUtils.
+ *
+ * @author Patrick
+ */
+public abstract class StringUtils extends org.apache.commons.lang3.StringUtils {
+
+ /**
+ * The String {@code "0"}.
+ */
+ public static final String ZERO = "0";
+
+ /**
+ * The String {@code "1"}.
+ */
+ public static final String ONE = "1";
+
+ /**
+ * The String {@code "%"}.
+ */
+ public static final String PERCENT = "%";
+
+ /**
+ * The String {@code ","}.
+ */
+ public static final String COMMA = ",";
+
+ /**
+ * The String {@code "\r\n"} for line break on Windows
+ */
+ public static final String LINE_BREAK_WINDOWS = "\r\n";
+
+ /**
+ * The String {@code "\n"} for line break on Unix/Linux
+ */
+ public static final String LINE_BREAK_LINUX = "\n";
+
+ public static final String[] A2Z_LOWWER = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
+ "w", "x", "y", "z" };
+ public static final String[] A2Z_UPPER = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
+ "W", "X", "Y", "Z" };
+
+ public static final String concat(String segregator, final String... chars) {
+ if (segregator == null) segregator = "";
+ String rs = "";
+ for (String c : chars) {
+ if (c == null)
+ continue;
+ else {
+ if (StringUtils.isBlank(rs)) {
+ rs = c;
+ } else {
+ rs += segregator + c;
+ }
+ }
+ }
+ return rs;
+ }
+}
diff --git a/src/main/java/com/ffii/core/utils/ZXingUtils.java b/src/main/java/com/ffii/core/utils/ZXingUtils.java
new file mode 100644
index 0000000..740fa8e
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/ZXingUtils.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core2 project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils;
+
+import java.awt.Image;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Writer;
+import com.google.zxing.WriterException;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.oned.Code128Writer;
+import com.google.zxing.qrcode.QRCodeWriter;
+
+/**
+ * ZXing Utils
+ *
+ * @author Patrick
+ */
+public abstract class ZXingUtils {
+
+ private static final Writer CODE128_WRITER = new Code128Writer();
+
+ private static final Writer QR_CODE_WRITER = new QRCodeWriter();
+
+ /**
+ * Encode contents (String) to Code 128 image
+ *
+ * @throws WriterException
+ */
+ public static Image encodeToImageCode128(String contents, int width, int height) throws WriterException {
+ BitMatrix matrix = CODE128_WRITER.encode(contents, BarcodeFormat.CODE_128, width, height);
+ return MatrixToImageWriter.toBufferedImage(matrix);
+ }
+
+ /**
+ * Encode contents (String) to QR Code image
+ *
+ * @throws WriterException
+ */
+ public static Image encodeToImageQRCode(String contents, int width, int height) throws WriterException {
+ BitMatrix matrix = QR_CODE_WRITER.encode(contents, BarcodeFormat.QR_CODE, width, height);
+ return MatrixToImageWriter.toBufferedImage(matrix);
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/utils/sql/QueryBuilder.java b/src/main/java/com/ffii/core/utils/sql/QueryBuilder.java
new file mode 100644
index 0000000..df3a992
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/sql/QueryBuilder.java
@@ -0,0 +1,114 @@
+package com.ffii.core.utils.sql;
+
+import java.util.Map;
+
+import com.ffii.core.utils.StringUtils;
+
+/**
+ * a query builder for handling paging
+ * suffix cannot blank
+ *
+ * paging arguments key:
+ * "start": integer (base at 0),
+ * "limit": integer
+ *
+ *
+ * @see #setPaging(boolean)
+ * @see #setPrefix(String)
+ * @see #setSuffix(String)
+ *
+ * @@author Fung
+ */
+public class QueryBuilder implements java.io.Serializable {
+ private static final long serialVersionUID = 4433680400177304974L;
+
+ // setting
+ private boolean paging = false;
+ private Map args;
+
+ /** "SELECT *" */
+ private String prefix;
+
+ /** "FROM xxxxxxx WHERE true" */
+ private String suffix;
+
+ /** default disable paging */
+ public QueryBuilder(){}
+ public QueryBuilder(boolean paging){
+ this.paging = paging;
+ }
+
+ @Override
+ public String toString() {
+ return getSearchSql();
+ }
+
+ public String getSearchSql() {
+ parameterCheck();
+ return getPrefix() + " " + getSuffix() + (this.paging ? limitQuery() : "");
+ }
+
+ /** column name "count" */
+ public String getTotalSql() {
+ parameterCheck();
+ return "SELECT COUNT(1) 'count'" + " " + getSuffix();
+ }
+
+ private void parameterCheck() {
+ if (StringUtils.isBlank(suffix))
+ throw new IllegalArgumentException("suffix of sql cannot blank");
+ }
+
+ private String limitQuery() {
+ if (args == null)
+ return StringUtils.EMPTY;
+ else if (args.containsKey("start") && args.containsKey("limit"))
+ return " LIMIT :start,:limit";
+ else if (args.containsKey("limit"))
+ return " LIMIT :limit";
+ else
+ return StringUtils.EMPTY;
+ }
+
+ // setter and getter
+
+ public boolean isPaging() {
+ return this.paging;
+ }
+
+ public boolean getPaging() {
+ return this.paging;
+ }
+
+ /** true for paging query, default false*/
+ public void setPaging(boolean paging) {
+ this.paging = paging;
+ }
+
+ public Map getArgs() {
+ return this.args;
+ }
+
+ public void setArgs(Map args) {
+ this.args = args;
+ }
+
+ public String getPrefix() {
+ return StringUtils.isNotBlank(this.prefix) ? this.prefix : "SELECT *";
+ }
+
+ /** "SELECT *" */
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public String getSuffix() {
+ return this.suffix;
+ }
+
+ /** "FROM xxxxxxxx WHERE true"
suffix cannot blank*/
+ public void setSuffix(String suffix) {
+ this.suffix = suffix;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/utils/web/ServletRequestUtils.java b/src/main/java/com/ffii/core/utils/web/ServletRequestUtils.java
new file mode 100644
index 0000000..48b7522
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/web/ServletRequestUtils.java
@@ -0,0 +1,336 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils.web;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
+import com.ffii.core.support.SimpleDateEditor;
+import com.ffii.core.support.SimpleIntegerEditor;
+import com.ffii.core.support.SimpleStringTrimToNullEditor;
+import com.ffii.core.utils.DateUtils;
+import com.ffii.core.utils.JsonUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.StringUtils;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.ServletRequestDataBinder;
+
+/**
+ * ServletRequestUtils (extends from Spring Framework)
+ *
+ * @author Patrick
+ */
+public abstract class ServletRequestUtils extends org.springframework.web.bind.ServletRequestUtils {
+
+ /**
+ * Mobile User-Agent Signatures
+ */
+ private static final String[] MOBILE_USER_AGENTS = { "Android", "iPhone", "iPad" };
+
+ /**
+ * Get the User-Agent from the request header
+ *
+ * @param request
+ * HttpServletRequest
+ *
+ * @return the User-Agent from the request header, or null
if not found
+ */
+ public static String getUserAgent(HttpServletRequest request) {
+ return request.getHeader("User-Agent");
+ }
+
+ /**
+ * Check if the User-Agent is mobile device
+ *
+ * @param request
+ * HttpServletRequest
+ *
+ * @return true
if the User-Agent is mobile device
+ */
+ public static boolean isMobileUserAgent(HttpServletRequest request) {
+ for (String MOBILE_USER_AGENT : MOBILE_USER_AGENTS) {
+ if (StringUtils.containsIgnoreCase(getUserAgent(request), MOBILE_USER_AGENT))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get a Map of request parameters
+ *
+ * @param request
+ * current HTTP request
+ *
+ * @return a Map containing all of the request parameters
+ */
+ public static Map getParameters(ServletRequest request) {
+ Enumeration params = request.getParameterNames();
+ Map paramsMap = new HashMap();
+ while (params.hasMoreElements()) {
+ String paramName = (String) params.nextElement();
+ String paramValue = request.getParameter(paramName);
+ paramsMap.put(paramName, paramValue);
+ }
+ return paramsMap;
+ }
+
+ /**
+ * Get an Integer parameter, with a fallback value. Never throws an exception. Can pass a distinguished value as default to enable checks of whether it was
+ * supplied.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ * @param defaultVal
+ * the default value to use as fallback
+ *
+ * @return the Integer value, or defaultVal
if not present OR if the value cannot be parsed
+ */
+ public static Integer getIntParameter(ServletRequest request, String name, Integer defaultVal) {
+ try {
+ return getIntParameter(request, name);
+ } catch (ServletRequestBindingException e) {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Get a Long parameter, with a fallback value. Never throws an exception. Can pass a distinguished value as default to enable checks of whether it was
+ * supplied.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ * @param defaultVal
+ * the default value to use as fallback
+ *
+ * @return the Long value, or defaultVal
if not present OR if the value cannot be parsed
+ */
+ public static Long getLongParameter(ServletRequest request, String name, Long defaultVal) {
+ try {
+ return getLongParameter(request, name);
+ } catch (ServletRequestBindingException e) {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Get a Double parameter, with a fallback value. Never throws an exception. Can pass a distinguished value as default to enable checks of whether it was
+ * supplied.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ * @param defaultVal
+ * the default value to use as fallback
+ *
+ * @return the Double value, or defaultVal
if not present OR if the value cannot be parsed
+ */
+ public static Double getDoubleParameter(ServletRequest request, String name, Double defaultVal) {
+ try {
+ return getDoubleParameter(request, name);
+ } catch (ServletRequestBindingException e) {
+ return defaultVal;
+ }
+ }
+
+ /**
+ * Get a trimmed (to null
if blank) String parameter, or null
if not present.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ *
+ * @return the trimmed (to null
if blank) String value, or null
if not present
+ *
+ * @throws ServletRequestBindingException
+ * a subclass of ServletException, so it doesn't need to be caught
+ */
+ public static String getTrimmedStringParameter(ServletRequest request, String name) throws ServletRequestBindingException {
+ return StringUtils.trimToNull(org.springframework.web.bind.ServletRequestUtils.getStringParameter(request, name));
+ }
+
+ /**
+ * Get a trimmed (to null
if blank) String parameter, with a fallback value. Never throws an exception. Can pass a distinguished value to
+ * default to enable checks of whether it was supplied.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ * @param defaultVal
+ * the default value to use as fallback
+ *
+ * @return the trimmed (to null
if blank) String value, or defaultVal
(will be trimmed to null
if blank) if not
+ * present
+ */
+ public static String getTrimmedStringParameter(ServletRequest request, String name, String defaultVal) {
+ return StringUtils.trimToNull(org.springframework.web.bind.ServletRequestUtils.getStringParameter(request, name, defaultVal));
+ }
+
+ /**
+ * Get a SQL Date parameter, or null
if not present or cannot be parsed.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ * @param defaultVal
+ * the default value to use as fallback
+ *
+ * @return the SQL Date value, or null
if not present or cannot be parsed
+ */
+ public static java.sql.Date getSqlDateParameter(ServletRequest request, String name) throws ServletRequestBindingException {
+ return getSqlDateParameter(request, name, null);
+ }
+
+ /**
+ * Get a SQL Date parameter, with a fallback value.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ * @param defaultVal
+ * the default value to use as fallback
+ *
+ * @return the SQL Date value, or default value if not present or cannot be parsed
+ */
+ public static java.sql.Date getSqlDateParameter(ServletRequest request, String name, java.sql.Date defaultVal) throws ServletRequestBindingException {
+ return DateUtils.toSqlDate(DateUtils.parseDateStrictly((getTrimmedStringParameter(request, name)), DateUtils.PARSE_PATTERNS, defaultVal));
+ }
+
+ /**
+ * Get a Date parameter, or null
if not present or cannot be parsed.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ * @param defaultVal
+ * the default value to use as fallback
+ *
+ * @return the Date value, or null
if not present or cannot be parsed
+ */
+ public static Date getDateParameter(ServletRequest request, String name) throws ServletRequestBindingException {
+ return getDateParameter(request, name, null);
+ }
+
+ /**
+ * Get a Date parameter, with a fallback value.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ * @param defaultVal
+ * the default value to use as fallback
+ *
+ * @return the Date value, or default value if not present or cannot be parsed
+ */
+ public static Date getDateParameter(ServletRequest request, String name, Date defaultVal) throws ServletRequestBindingException {
+ return DateUtils.parseDateStrictly((getTrimmedStringParameter(request, name)), DateUtils.PARSE_PATTERNS, defaultVal);
+ }
+
+ /**
+ * Get a List of Map from JSON string parameter.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ *
+ * @return a List of Map, or an empty List if the parameter is not present
+ */
+ public static List> getJsonListParameter(ServletRequest request, String name) throws ServletRequestBindingException, IOException {
+ String content = getStringParameter(request, name);
+ if (content != null)
+ return JsonUtils.fromJsonString(content, List.class);
+ else
+ return Collections.EMPTY_LIST;
+ }
+
+ /**
+ * Get a Map from JSON string parameter.
+ *
+ * @param request
+ * current HTTP request
+ * @param name
+ * the name of the parameter
+ *
+ * @return a Map, or an empty Map if the parameter is not present
+ */
+ public static Map,?> getJsonMapParameter(ServletRequest request, String name) throws ServletRequestBindingException, IOException {
+ String content = getStringParameter(request, name);
+ if (content != null)
+ return JsonUtils.fromJsonString(content, Map.class);
+ else
+ return Collections.EMPTY_MAP;
+ }
+
+ /**
+ * Binds data using {@link ServletRequestDataBinder} with the following custom editors:-
+ *
+ *
+ * {@link SimpleStringTrimToNullEditor}
+ * {@link SimpleIntegerEditor}
+ * {@link SimpleDateEditor}
+ *
+ *
+ *
+ * Important: The following system fields will NOT be binded for security reasons.
+ *
+ * id
+ * ownerId
+ * deleted
+ * createdBy
+ * modifiedBy
+ * created
+ * modified
+ * password
+ *
+ *
+ *
+ * @param request
+ * current HTTP request
+ * @param object
+ * the target object to bind onto
+ * @param objectName
+ * the name of the target object
+ * @param disallowFields
+ * optional
+ */
+ public static Object doBind(ServletRequest request, Object object, String objectName, String... disallowFields) {
+ ServletRequestDataBinder binder = new ServletRequestDataBinder(object, objectName);
+ binder.registerCustomEditor(String.class, new SimpleStringTrimToNullEditor());
+ binder.registerCustomEditor(Integer.class, new SimpleIntegerEditor());
+ binder.registerCustomEditor(java.util.Date.class, new SimpleDateEditor());
+ String[] coreFields = { Params.ID, "ownerId", "deleted", "createdBy", "modifiedBy", "created", "modified", "password" };
+ binder.setDisallowedFields(ArrayUtils.addAll(coreFields, disallowFields));
+ binder.bind(request);
+ return object;
+ };
+
+}
diff --git a/src/main/java/com/ffii/core/utils/web/ServletResponseUtils.java b/src/main/java/com/ffii/core/utils/web/ServletResponseUtils.java
new file mode 100644
index 0000000..1fd6fec
--- /dev/null
+++ b/src/main/java/com/ffii/core/utils/web/ServletResponseUtils.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.utils.web;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * ServletResponseUtils
+ *
+ * @author Patrick
+ */
+public abstract class ServletResponseUtils {
+
+ /**
+ * Writes a String to HttpServletResponse encoded with the specified charsetName, and then flush and close it.
+ *
+ * @param response
+ * HttpServletResponse
+ * @param charsetName
+ * The name of a supported {@link java.nio.charset.Charset
charset}
+ * @param str
+ * String to be written
+ */
+ public static void writeStringToStream(HttpServletResponse response, String charsetName, String str) throws UnsupportedEncodingException, IOException {
+ OutputStreamWriter out = new OutputStreamWriter(response.getOutputStream(), charsetName);
+ out.write(str);
+ out.flush();
+ out.close();
+ }
+
+ /**
+ * Writes bytes to HttpServletResponse, and then flush and close it.
+ *
+ * @param response
+ * HttpServletResponse
+ * @param bytes
+ * The bytes array to be written
+ * @throws IOException
+ */
+ public static void writeBytesToStream(HttpServletResponse response, byte[] bytes) throws IOException {
+ ServletOutputStream out = response.getOutputStream();
+ out.write(bytes);
+ out.flush();
+ out.close();
+ }
+
+ /**
+ * Disables caching by modifying the response header.
+ *
+ * @param response
+ * HttpServletResponse
+ */
+ public static void disableCaching(HttpServletResponse response) {
+ response.addHeader("Pragma", "no-cache");
+ response.addHeader("Cache-Control", "no-cache, no-store, max-age=0");
+ response.addDateHeader("Expires", 1L);
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/web/AbstractController.java b/src/main/java/com/ffii/core/web/AbstractController.java
new file mode 100644
index 0000000..a63db7a
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/AbstractController.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web;
+
+import org.springframework.web.context.support.WebApplicationObjectSupport;
+
+public abstract class AbstractController extends WebApplicationObjectSupport {
+
+}
diff --git a/src/main/java/com/ffii/core/web/AbstractService.java b/src/main/java/com/ffii/core/web/AbstractService.java
new file mode 100644
index 0000000..91a3ff2
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/AbstractService.java
@@ -0,0 +1,17 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web;
+
+import org.springframework.web.context.support.WebApplicationObjectSupport;
+
+public abstract class AbstractService extends WebApplicationObjectSupport {
+
+}
diff --git a/src/main/java/com/ffii/core/web/AppConfig.java b/src/main/java/com/ffii/core/web/AppConfig.java
new file mode 100644
index 0000000..4b9b5ba
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/AppConfig.java
@@ -0,0 +1,10 @@
+package com.ffii.core.web;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@Configuration
+@EnableTransactionManagement
+public class AppConfig {
+
+}
diff --git a/src/main/java/com/ffii/core/web/ControllerAdvice.java b/src/main/java/com/ffii/core/web/ControllerAdvice.java
new file mode 100644
index 0000000..14d3513
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/ControllerAdvice.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web;
+
+import org.springframework.web.bind.WebDataBinder;
+import org.springframework.web.bind.annotation.InitBinder;
+
+import com.ffii.core.support.SimpleDateEditor;
+import com.ffii.core.support.SimpleIntegerEditor;
+import com.ffii.core.support.SimpleStringTrimToNullEditor;
+
+/**
+ * ControllerAdvice is used to define @ExceptionHandler, @InitBinder, and @ModelAttribute methods that apply to all @RequestMapping methods.
+ *
+ * @author Patrick
+ */
+@org.springframework.web.bind.annotation.ControllerAdvice
+public class ControllerAdvice {
+
+ @InitBinder
+ public void initBinder(WebDataBinder binder) {
+ binder.registerCustomEditor(String.class, new SimpleStringTrimToNullEditor());
+ binder.registerCustomEditor(Integer.class, new SimpleIntegerEditor());
+ binder.registerCustomEditor(java.util.Date.class, new SimpleDateEditor());
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/web/RestResponseEntityExceptionHandler.java b/src/main/java/com/ffii/core/web/RestResponseEntityExceptionHandler.java
new file mode 100644
index 0000000..ffc40dd
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/RestResponseEntityExceptionHandler.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web;
+
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+/**
+ * @author Patrick
+ */
+@org.springframework.web.bind.annotation.ControllerAdvice
+public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ @ExceptionHandler(value = { DataIntegrityViolationException.class })
+ protected ResponseEntity handleDataIntegrityViolationException(RuntimeException ex, WebRequest request) {
+ logger.error(ex.getMessage());
+ return handleExceptionInternal(ex, ExceptionUtils.getRootCauseMessage(ex), null, HttpStatus.INTERNAL_SERVER_ERROR, request);
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/web/filter/AjaxSessionExpirationFilter.java b/src/main/java/com/ffii/core/web/filter/AjaxSessionExpirationFilter.java
new file mode 100644
index 0000000..84a9851
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/filter/AjaxSessionExpirationFilter.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web.filter;
+
+import java.io.IOException;
+
+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.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+public class AjaxSessionExpirationFilter implements Filter {
+
+ private int customSessionExpiredErrorCode = 403;
+
+ @Override
+ public void init(FilterConfig cfg) throws ServletException {
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ HttpSession currentSession = ((HttpServletRequest) request).getSession(false);
+ if (currentSession == null) {
+ String requestedWithHeader = ((HttpServletRequest) request).getHeader("X-Requested-With");
+ if ("XMLHttpRequest".equals(requestedWithHeader)) {
+ ((HttpServletResponse) response).sendError(this.customSessionExpiredErrorCode);
+ } else {
+ chain.doFilter(request, response);
+ }
+ } else {
+ chain.doFilter(request, response);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/web/view/AbstractView.java b/src/main/java/com/ffii/core/web/view/AbstractView.java
new file mode 100644
index 0000000..ed7fbb4
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/view/AbstractView.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web.view;
+
+import org.springframework.web.servlet.View;
+
+/**
+ * Abstract base class extending Spring {@link AbstractView} for {@link View} implementations.
+ *
+ * @see View
+ * @author Patrick
+ */
+public abstract class AbstractView extends org.springframework.web.servlet.view.AbstractView {
+
+ public static final String CONTENT_TYPE_JSON = "application/json";
+
+ public static final String CONTENT_TYPE_JAVASCRIPT = "text/javascript";
+
+ public static final String CONTENT_TYPE_TEXT_HTML = "text/html";
+ public static final String CONTENT_TYPE_TEXT_PLAIN = "text/plain";
+ public static final String CONTENT_TYPE_XML = "text/xml";
+
+ public static final String CONTENT_TYPE_JPEG = "image/jpeg";
+ public static final String CONTENT_TYPE_PNG = "image/png";
+
+ public static final String CHARSET_UTF8 = "UTF-8";
+
+ protected boolean disableCaching = true;
+
+ /**
+ * Disables caching of the generated JSON.
+ * Default is {@code true}, which will prevent the client from caching the generated JSON.
+ */
+ public void setDisableCaching(boolean disableCaching) {
+ this.disableCaching = disableCaching;
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/web/view/document/AbstractExcelView.java b/src/main/java/com/ffii/core/web/view/document/AbstractExcelView.java
new file mode 100644
index 0000000..70fd9c5
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/view/document/AbstractExcelView.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web.view.document;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.LocalizedResourceHelper;
+import org.springframework.web.servlet.support.RequestContextUtils;
+
+public abstract class AbstractExcelView extends org.springframework.web.servlet.view.document.AbstractXlsxView {
+
+ private String url;
+
+ /**
+ * Set the URL of the Excel Workbook source, without localization part nor .xlsx extension.
+ */
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ /**
+ * Creates the Excel Workbook from an existing XLSX document.
+ *
+ * @param url
+ * the URL of the Excel template without localization part nor extension
+ * @param request
+ * current HTTP request
+ * @return the Workbook
+ *
+ * @throws IOException
+ * in case of failure
+ */
+ private Workbook getTemplateSource(String url, HttpServletRequest request) throws IOException {
+ LocalizedResourceHelper helper = new LocalizedResourceHelper(getApplicationContext());
+ Locale userLocale = RequestContextUtils.getLocale(request);
+ Resource inputFile = helper.findLocalizedResource(url, ".xlsx", userLocale);
+
+ // Create the Excel Workbook from the source.
+ if (logger.isDebugEnabled()) {
+ logger.debug("Loading Excel workbook from " + inputFile);
+ }
+ return new XSSFWorkbook(inputFile.getInputStream());
+ }
+
+ /**
+ * This implementation creates an {@link XSSFWorkbook} for the XLSX format either from URL (if provided) or from scratch.
+ */
+ @Override
+ protected Workbook createWorkbook(Map model, HttpServletRequest request) {
+ Workbook workbook;
+
+ if (this.url != null) {
+ try {
+ workbook = getTemplateSource(this.url, request);
+ } catch (IOException e) {
+ logger.error("Failed to load Excel Workbook from URL!", e);
+ workbook = new XSSFWorkbook();
+ }
+ } else {
+ workbook = new XSSFWorkbook();
+ logger.debug("Created Excel Workbook from scratch");
+ }
+
+ return workbook;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/web/view/freemarker/FreeMarkerView.java b/src/main/java/com/ffii/core/web/view/freemarker/FreeMarkerView.java
new file mode 100644
index 0000000..6a5a63f
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/view/freemarker/FreeMarkerView.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web.view.freemarker;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.security.web.csrf.CsrfToken;
+
+import com.ffii.core.setting.service.SettingsService;
+import com.ffii.core.utils.SecurityUtils;
+
+import freemarker.template.SimpleHash;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+
+/**
+ * FreeMarker view which extends Spring's {@link org.springframework.web.servlet.view.freemarker.FreeMarkerView}.
+ *
+ * Enforce response's character encoding using {@link ServletResponse#setCharacterEncoding(String)}.
+ *
+ * Expose {@link SettingsService} and {@link SecurityUtils} for use in FreeMarker template.
+ *
+ * Use with {@link FreeMarkerViewResolver}.
+ *
+ * @author Patrick
+ */
+public class FreeMarkerView extends org.springframework.web.servlet.view.freemarker.FreeMarkerView {
+
+ private String responseEncoding;
+
+ private SettingsService settingsService;
+
+ /** SecurityUtils for use in FreeMarker template */
+ private static final SecurityUtils SECURITY_UTILS = new SecurityUtils();
+
+ public String getResponseEncoding() {
+ return responseEncoding;
+ }
+
+ public void setResponseEncoding(String responseEncoding) {
+ this.responseEncoding = responseEncoding;
+ }
+
+ public SettingsService getSettingsService() {
+ return settingsService;
+ }
+
+ public void setSettingsService(SettingsService settingsService) {
+ this.settingsService = settingsService;
+ }
+
+ @Override
+ protected void processTemplate(Template template, SimpleHash model, HttpServletResponse response) throws IOException, TemplateException {
+ response.setCharacterEncoding(getResponseEncoding());
+ super.processTemplate(template, model, response);
+ }
+
+ @Override
+ protected void exposeHelpers(Map model, HttpServletRequest request) throws Exception {
+ CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
+ if (csrf != null)
+ model.put("_csrf", csrf.getToken());
+ else
+ model.put("_csrf", "");
+ model.put("SecurityUtils", SECURITY_UTILS);
+ model.put("SettingsService", settingsService);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/core/web/view/freemarker/FreeMarkerViewResolver.java b/src/main/java/com/ffii/core/web/view/freemarker/FreeMarkerViewResolver.java
new file mode 100644
index 0000000..c785a25
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/view/freemarker/FreeMarkerViewResolver.java
@@ -0,0 +1,83 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web.view.freemarker;
+
+
+import com.ffii.core.setting.service.SettingsService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.servlet.view.AbstractTemplateViewResolver;
+import org.springframework.web.servlet.view.AbstractUrlBasedView;
+
+/**
+ * FreeMarker view resolver which extends Spring's {@link AbstractTemplateViewResolver}.
+ *
+ * Allows response's character encoding to be explicitly set (default to UTF-8
if not set), so that it is not necessary to set it together at the
+ * {@link #setContentType(String)} method.
+ *
+ * The default suffix is set to .ftl
+ *
+ * @see FreeMarkerView
+ *
+ * @author Patrick
+ */
+public class FreeMarkerViewResolver extends AbstractTemplateViewResolver {
+
+ /** Default response's character encoding is UTF-8
*/
+ private static final String DEFAULT_RESPONSE_ENCODING = "UTF-8";
+
+ /** Default suffix for FreeMarker template is .ftl
*/
+ private static final String DEFAULT_SUFFIX = ".ftl";
+
+ private String responseEncoding = DEFAULT_RESPONSE_ENCODING;
+
+ @Autowired
+ private SettingsService settingsService;
+
+ public String getResponseEncoding() {
+ return responseEncoding;
+ }
+
+ public void setResponseEncoding(String responseEncoding) {
+ this.responseEncoding = responseEncoding;
+ }
+
+ /**
+ * Default constructor
+ *
+ * 1. Sets default viewClass to {@link #requiredViewClass}.
+ * 2. Sets default suffix to {@link #DEFAULT_SUFFIX}
+ *
+ * @see #setViewClass
+ * @see #setSuffix
+ */
+ public FreeMarkerViewResolver() {
+ setViewClass(requiredViewClass());
+ setSuffix(DEFAULT_SUFFIX);
+ }
+
+ /**
+ * Requires {@link FreeMarkerView}.
+ */
+ @Override
+ protected Class> requiredViewClass() {
+ return FreeMarkerView.class;
+ }
+
+ @Override
+ protected AbstractUrlBasedView buildView(String viewName) throws Exception {
+ FreeMarkerView view = (FreeMarkerView) super.buildView(viewName);
+ view.setResponseEncoding(getResponseEncoding());
+ view.setSettingsService(settingsService);
+ return view;
+ }
+
+}
diff --git a/src/main/java/com/ffii/core/web/view/jasperreports/JasperReportsMultiFormatView.java b/src/main/java/com/ffii/core/web/view/jasperreports/JasperReportsMultiFormatView.java
new file mode 100644
index 0000000..80cad1d
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/view/jasperreports/JasperReportsMultiFormatView.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web.view.jasperreports;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.web.servlet.view.jasperreports.AbstractJasperReportsView;
+import org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView;
+
+/**
+ *
+ *
+ * The default mappings for the format lookup are:
+ *
+ *
+ *
+ * {@code pdf} - {@code JasperReportsPdfView}
+ * {@code docx} - {@code JasperReportsDocxView}
+ *
+ *
+ */
+public class JasperReportsMultiFormatView extends org.springframework.web.servlet.view.jasperreports.JasperReportsMultiFormatView {
+
+ public JasperReportsMultiFormatView() {
+ Map> formatMappings = new HashMap>(1);
+ formatMappings.put("pdf", JasperReportsPdfView.class);
+ // formatMappings.put("docx", JasperReportsDocxView.class);
+ setFormatMappings(formatMappings);
+ }
+}
diff --git a/src/main/java/com/ffii/core/web/view/json/JsonView.java b/src/main/java/com/ffii/core/web/view/json/JsonView.java
new file mode 100644
index 0000000..0fff2f1
--- /dev/null
+++ b/src/main/java/com/ffii/core/web/view/json/JsonView.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the Core project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.core.web.view.json;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.servlet.View;
+
+import com.ffii.core.utils.JsonUtils;
+import com.ffii.core.utils.StringUtils;
+import com.ffii.core.utils.web.ServletResponseUtils;
+import com.ffii.core.web.view.AbstractView;
+
+/**
+ * {@link View} implementation to render the specified view data model as a JSON response.
+ *
+ * @see View
+ * @author Patrick
+ */
+public class JsonView extends AbstractView {
+
+ /**
+ * Default view name of JsonView bean
+ */
+ public static final String NAME = "jsonView";
+
+ @Override
+ protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
+ // set response content type and encoding
+ if (StringUtils.contains(request.getHeader("Content-Type"), "multipart/form-data"))
+ response.setContentType(CONTENT_TYPE_TEXT_HTML);
+ else
+ response.setContentType(CONTENT_TYPE_JSON);
+ response.setCharacterEncoding(CHARSET_UTF8);
+ if (disableCaching) {
+ ServletResponseUtils.disableCaching(response);
+ }
+ }
+
+ @Override
+ protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
+ // create json string from model
+ String jsonString = JsonUtils.toJsonString(model);
+
+ // encode json string and write to stream
+ ServletResponseUtils.writeStringToStream(response, CHARSET_UTF8, jsonString);
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/api/service/AuthenticationService.java b/src/main/java/com/ffii/tls/api/service/AuthenticationService.java
new file mode 100644
index 0000000..25c9d9d
--- /dev/null
+++ b/src/main/java/com/ffii/tls/api/service/AuthenticationService.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.api.service;
+
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.web.AbstractService;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.jfree.util.Log;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Authentication API Service
+ *
+ * @author Patrick
+ */
+@Service
+public class AuthenticationService extends AbstractService {
+
+ private static SecureRandom SECURE_RANDOM = new SecureRandom();
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ /**
+ * Generate random token
+ *
+ * @return 32 characters random String with letters and numbers
+ */
+ private String generateToken() {
+ return RandomStringUtils.random(32, 0, 0, true, true, null, SECURE_RANDOM);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public String createAccessToken(int userId, String deviceId) {
+
+ String sql = "INSERT INTO";
+ sql += " access_token";
+ sql += " (userId, deviceId, token, created)";
+ sql += " VALUES";
+ sql += " (:userId, :deviceId, :token, NOW())";
+ sql += " ON DUPLICATE KEY";
+ sql += " UPDATE";
+ sql += " created = NOW(),";
+ sql += " token = :token";
+
+ String token = generateToken();
+
+ Map args = MapUtils.toHashMap("userId", userId, "deviceId", deviceId, "token", token);
+ jdbcDao.executeUpdate(sql, args);
+
+ return token;
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public Map findUserByAccessToken(String deviceId, String token) {
+
+ String sql = "SELECT";
+ sql += " u.id,";
+ sql += " u.sysGroupId,";
+ sql += " u.expired,";
+ sql += " u.locked,";
+ sql += " u.companyId,";
+ sql += " u.username,";
+ sql += " u.firstname,";
+ sql += " u.lastname,";
+ sql += " u.fullname";
+ sql += " FROM access_token at";
+ sql += " LEFT JOIN users u ON at.userId = u.id";
+ sql += " WHERE u.deleted = 0";
+ sql += " AND u.locked = 0";
+ sql += " AND at.deviceId = :deviceId";
+ sql += " AND at.token = :token";
+
+ Map args = MapUtils.toHashMap("deviceId", deviceId, "token", token);
+ Map data = jdbcDao.queryForMap(sql, args);
+ if (data == null){
+ logger.info("userData : null, deviceId: "+deviceId+", token: "+token);
+ }
+ return data;
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public Map findUserAuth(Map args) {
+
+ String sql = " SELECT * "
+ + " FROM users_authorities ua "
+ + " WHERE ua.userId = :userId "
+ + " AND ua.authority = :auth ; ";
+
+ return jdbcDao.queryForMap(sql, args);
+ }
+
+ public boolean haveAuth(Object userId, String... auths){
+ boolean haveAuth = false;
+
+ Map authMap = findUserAuth(MapUtils.toHashMap("userId", userId, "auth", "SUPERUSER"));
+ haveAuth = authMap != null && authMap.get("userId") == userId;
+ if(haveAuth) return haveAuth;
+
+ for(String auth:auths){
+ authMap = findUserAuth(MapUtils.toHashMap("userId", userId, "auth", auth));
+ haveAuth = authMap != null && authMap.get("userId") == userId;
+ if(haveAuth) return haveAuth;
+ }
+ return haveAuth;
+ }
+
+ public Map getNoUserFoundMap(){
+ logger.info("NoUserFound");
+ Map map = new HashMap<>();
+ map.put(Params.AUTH, Boolean.FALSE);
+ map.put(Params.SUCCESS, Boolean.FALSE);
+ map.put(Params.MSG, "pleaseReLogin");
+ return map;
+ }
+
+ public Map getNoAuthFoundMap(){
+ logger.info("NoAuth");
+ Map map = new HashMap<>();
+ map.put(Params.AUTH, Boolean.FALSE);
+ map.put(Params.SUCCESS, Boolean.FALSE);
+ return map;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/api/service/CustomerApiService.java b/src/main/java/com/ffii/tls/api/service/CustomerApiService.java
new file mode 100644
index 0000000..0b5f945
--- /dev/null
+++ b/src/main/java/com/ffii/tls/api/service/CustomerApiService.java
@@ -0,0 +1,214 @@
+package com.ffii.tbms.api.service;
+
+import java.util.List;
+import java.util.Map;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.web.AbstractService;
+import com.ffii.tbms.customer.Customer;
+import com.ffii.tbms.customer.dao.CustomerDao;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * OrderApiService
+ */
+@Service
+public class CustomerApiService extends AbstractService {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ @Autowired
+ private CustomerDao customerDao;
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveOrUpdate(Customer instance) {
+ return customerDao.saveOrUpdate(instance);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public Customer find(Integer id) {
+ return customerDao.find(id);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> searchCustomerList(Map args) {
+
+ StringBuilder sql = new StringBuilder(
+ " SELECT "
+ +" c.id,"
+ +" c.surname, "
+ +" c.firstName, "
+ +" c.nameCh, "
+ +" CONCAT(IF(c.firstName IS NULL, '', CONCAT(c.firstName, ' ')), "
+ +" IF(c.surname IS NULL, '', CONCAT(c.surname, ' ')), "
+ +" IF(c.nameCh IS NULL, '', c.nameCh)) AS customerName, "
+ +" concat_ws(' / ', c.phone1, c.phone2) AS phone, "
+ +" c.phone1, "
+ +" c.phone2, "
+ +" c.companyPhone, "
+ +" c.companyEmail, "
+ +" c.companyAddress, "
+ +" c.email, "
+ +" c.address, "
+ +" c.remarks, "
+ +" c.tag "
+ +" "
+ +" FROM "
+ +" customer c "
+
+ +" WHERE"
+ +" c.deleted = 0 ");
+
+ if(args != null){
+ if (args.containsKey("_searchStr")){
+ sql.append(getCustomerNameSearch(args.get("searchStr").toString()));
+ /*
+ sql.append(" AND ( c.surname LIKE '"+args.get("_searchStr")+"' ");
+ sql.append(" OR c.firstName LIKE '"+args.get("_searchStr")+"' ");
+ sql.append(" OR c.nameCh LIKE '"+args.get("_searchStr")+"' ");
+ sql.append(" OR c.phone1 LIKE '"+args.get("_searchStr")+"' ");
+ sql.append(" OR c.phone2 LIKE '"+args.get("_searchStr")+"' ) ");
+ */
+ }
+
+ if (args.containsKey("sysGroupId")){
+ sql.append(" AND c.sysGroupId = :sysGroupId ");
+ }
+ }
+
+ sql.append(" ORDER BY ( 0 ");
+
+ if (args != null) {
+ if (args.containsKey("searchStr")){
+ sql.append(getCustomerNameOrderBy(args.get("searchStr").toString()));
+ /*
+ sql.append(" + IF( c.surname = :searchStr,1,0) + IF( c.surname LIKE '"+args.get("searchStr")+"%',1,0) ");
+ sql.append(" + IF( c.firstName = :searchStr,1,0) + IF( c.firstName LIKE '"+args.get("searchStr")+"%',1,0) ");
+ sql.append(" + IF( c.nameCh = :searchStr,1,0) + IF( c.nameCh LIKE '"+args.get("searchStr")+"%',1,0) ");
+ sql.append(" + IF( c.phone1 = :searchStr,1,0) + IF( c.phone1 LIKE '"+args.get("searchStr")+"%',1,0) ");
+ sql.append(" + IF( c.phone2 = :searchStr,1,0) + IF( c.phone2 LIKE '"+args.get("searchStr")+"%',1,0) ");
+ sql.append(" + IF( c.companyPhone = :searchStr,1,0) + IF( c.companyPhone LIKE '"+args.get("searchStr")+"%',1,0) ");
+ */
+ }
+ }
+ sql.append(" ) DESC, ");
+ sql.append(" TRIM(c.firstName), TRIM(c.surname), TRIM(c.nameCh), c.id ");
+
+ logger.info("search cust: "+sql);
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ private String getCustomerNameSearch(String searchStr){
+ String str = "";
+
+ String[] array = searchStr.split(" ");
+ for(int i = 0; i loadCustomer(Map args) {
+
+ StringBuilder sql = new StringBuilder(
+ " SELECT "
+ +" c.id,"
+ +" c.surname, "
+ +" c.firstName, "
+ +" c.nameCh, "
+ +" CONCAT(IF(c.firstName IS NULL, '', CONCAT(c.firstName, ' ')), "
+ +" IF(c.surname IS NULL, '', CONCAT(c.surname, ' ')), "
+ +" IF(c.nameCh IS NULL, '', c.nameCh)) AS customerName, "
+ +" c.phone1, "
+ +" c.phone2, "
+ +" concat_ws(' / ', c.phone1, c.phone2) AS phone, "
+ +" c.companyPhone, "
+ +" c.companyEmail, "
+ +" c.companyAddress, "
+ +" c.email, "
+ +" c.address, "
+ +" c.remarks, "
+ +" c.tag, "
+ +" lastMeetingDate.date As lastMeetingDate, "
+ +" nextMeetingDate.date AS nextMeetingDate "
+ +" FROM "
+ +" customer c "
+
+ +" LEFT JOIN "
+ +" ( SELECT Max(m.date) AS date, m.custId "
+ +" FROM meeting m "
+ +" WHERE m.deleted = 0 "
+ +" AND DATE(m.date) < DATE(current_date()) "
+ +" GROUP BY m.custId ) lastMeetingDate ON lastMeetingDate.custId = c.id "
+
+ +" LEFT JOIN "
+ +" ( SELECT MIN(m.date) AS date, m.custId "
+ +" FROM meeting m "
+ +" WHERE m.deleted = 0 "
+ +" AND DATE(m.date) >= DATE(current_date()) "
+ +" GROUP BY m.custId ) nextMeetingDate ON nextMeetingDate.custId = c.id "
+
+ +" WHERE"
+ +" c.deleted = 0 AND c.id = :custId ");
+
+ return jdbcDao.queryForMap(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> searchForCombo(Map args) {
+
+ StringBuilder sql = new StringBuilder(" SELECT "
+ +" c.id, "
+ +" c.phone1, "
+ +" c.phone2, "
+ +" concat_ws(' / ', c.phone1, c.phone2) AS phone, "
+ +" CONCAT(IF(c.firstName IS NULL, '', CONCAT(c.firstName, ' ')), "
+ +" IF(c.surname IS NULL, '', CONCAT(c.surname, ' ')), "
+ +" IF(c.nameCh IS NULL, '', c.nameCh)) AS customerName "
+ +" FROM customer c WHERE deleted = 0 ");
+
+ if (args.containsKey(Params.QUERY))
+ sql.append(" AND ( concat_ws(' ', c.firstName ,c.surname, c.nameCh, c.phone1, c.phone2) LIKE :query "
+ +" OR concat_ws(' ', c.surname, c.firstName, c.nameCh, phone1, phone2) LIKE :query ) ");
+
+ if (args.containsKey(Params.ID))
+ sql.append(" AND c.id = :id");
+
+ if(args.containsKey("sysGroupId")){
+ sql.append(" AND c.sysGroupId = :sysGroupId ");
+ }
+
+ sql.append(" ORDER BY c.surname, c.firstName ");
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/tls/api/service/MeetingApiService.java b/src/main/java/com/ffii/tls/api/service/MeetingApiService.java
new file mode 100644
index 0000000..4026449
--- /dev/null
+++ b/src/main/java/com/ffii/tls/api/service/MeetingApiService.java
@@ -0,0 +1,108 @@
+package com.ffii.tbms.api.service;
+
+import java.util.List;
+import java.util.Map;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.web.AbstractService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * OrderApiService
+ */
+@Service
+public class MeetingApiService extends AbstractService {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> searchMeeting(String dateStr) {
+
+ StringBuilder sql = new StringBuilder("SELECT"
+ + " m.date AS meetingDate,"
+ + " m.id AS meetingId,"
+ + " m.type AS meetingType,"
+ + " cu.id AS custId,"
+ + " CONCAT(IF(cu.firstName IS NULL, '', CONCAT(cu.firstName, ' ')), "
+ + " IF(cu.surname IS NULL, '', CONCAT(cu.surname, ' ')), "
+ + " IF(cu.nameCh IS NULL, '', cu.nameCh)) AS customerName, "
+ + " m.orderId,"
+ + " CONCAT(o.type, o.code) as orderCode,"
+ + " m.type,"
+ + " m.remarks"
+ + " FROM meeting m"
+ + " LEFT JOIN customer cu ON cu.id = m.custId"
+ + " LEFT JOIN orders o ON o.id = m.orderId"
+ + " WHERE m.deleted =0 ");
+
+ if (dateStr != null) {
+ sql.append(" AND DATE(m.date) = :dateStr");
+ }
+
+ sql.append(" ORDER BY m.date ASC, m.id ASC ");
+
+ return jdbcDao.queryForList(sql.toString(), MapUtils.toHashMap("dateStr", dateStr));
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public Map loadMeeting(Map args) {
+
+ StringBuilder sql = new StringBuilder("SELECT"
+ + " m.date AS meetingDate,"
+ + " m.id AS meetingId,"
+ + " m.type AS meetingType,"
+ + " cu.id AS custId,"
+ + " CONCAT(IF(cu.firstName IS NULL, '', CONCAT(cu.firstName, ' ')), "
+ + " IF(cu.surname IS NULL, '', CONCAT(cu.surname, ' ')), "
+ + " IF(cu.nameCh IS NULL, '', cu.nameCh)) AS customerName, "
+ + " m.orderId,"
+ + " CONCAT(o.type, o.code) as orderCode,"
+ + " m.type,"
+ + " m.staffName,"
+ + " m.remarks"
+ + " FROM meeting m"
+ + " LEFT JOIN customer cu ON cu.id = m.custId"
+ + " LEFT JOIN orders o ON o.id = m.orderId"
+ + " WHERE m.deleted =0 AND m.id = :id ");
+
+ return jdbcDao.queryForMap(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> searchMeetingByDate(Map args) {
+
+ StringBuilder sql = new StringBuilder("SELECT"
+ + " m.date AS meetingDate,"
+ + " m.id AS meetingId,"
+ + " m.type AS meetingType,"
+ + " cu.id AS custId,"
+ + " CONCAT(IF(cu.firstName IS NULL, '', CONCAT(cu.firstName, ' ')), "
+ + " IF(cu.surname IS NULL, '', CONCAT(cu.surname, ' ')), "
+ + " IF(cu.nameCh IS NULL, '', cu.nameCh)) AS customerName, "
+ + " m.orderId,"
+ + " CONCAT(o.type, o.code) as orderCode,"
+ + " m.type,"
+ + " m.remarks"
+ + " FROM meeting m"
+ + " LEFT JOIN customer cu ON cu.id = m.custId"
+ + " LEFT JOIN orders o ON o.id = m.orderId"
+ + " WHERE m.deleted =0");
+
+ if (args.containsKey("selectedDate")) {
+ sql.append(" AND DATE(m.date) = :selectedDate");
+ }
+
+ sql.append(" ORDER BY m.date ASC, m.id ASC ");
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/tls/api/service/OrderApiService.java b/src/main/java/com/ffii/tls/api/service/OrderApiService.java
new file mode 100644
index 0000000..06146da
--- /dev/null
+++ b/src/main/java/com/ffii/tls/api/service/OrderApiService.java
@@ -0,0 +1,237 @@
+package com.ffii.tbms.api.service;
+
+import java.util.List;
+import java.util.Map;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.web.AbstractService;
+import com.ffii.tbms.order.Order;
+import com.ffii.tbms.order.dao.OrderDao;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * OrderApiService
+ */
+@Service
+public class OrderApiService extends AbstractService {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ @Autowired
+ private OrderDao orderDao;
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveOrUpdate(final Order instance) {
+ return orderDao.saveOrUpdate(instance);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public Order find(final Integer id) {
+ return orderDao.find(id);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> search(Map args) {
+
+ StringBuilder sql = new StringBuilder(" SELECT "
+ + " orders.id,"
+ + " orders.date,"
+ + " orders.type,"
+ + " orders.code,"
+ + " orders.targetCompleteDate,"
+ + " orders.status,"
+ + " orders.remarks,"
+ + " _p.paymentAmount, "
+ + " _oi.orderAmount, "
+ + " CONCAT(IF(cu.firstName IS NULL, '', CONCAT(cu.firstName, ' ')),"
+ + " IF(cu.surname IS NULL, '', CONCAT(cu.surname, ' ')),"
+ + " IF(cu.nameCh IS NULL, '', cu.nameCh)) AS customerName "
+ + " FROM orders"
+ + " LEFT JOIN customer cu ON cu.id = orders.custId"
+ +" LEFT JOIN (SELECT oi.orderId, SUM(oi.price) AS orderAmount, "
+ +" GROUP_CONCAT(oi.name SEPARATOR ' ') AS itemName "
+ +" FROM order_item oi WHERE oi.deleted = 0 "
+ +" GROUP BY oi.orderId) _oi ON _oi.orderId = orders.id "
+ +" LEFT JOIN ( "
+ +" SELECT "
+ +" SUM(p.amount) AS paymentAmount, "
+ +" p.orderId "
+ +" FROM payment p "
+ +" WHERE p.deleted = 0 "
+ +" GROUP BY p.orderId) _p ON _p.orderId = orders.id "
+
+ + " WHERE orders.deleted = 0 ");
+
+ if (args != null) {
+ if (args.containsKey("searchStr")) {
+ sql.append(
+ " AND (CONCAT(IFNULL(cu.firstName, ''), "
+ + " IFNULL(cu.surname, ''), "
+ + " IFNULL(cu.nameCh,''), "
+ + " IFNULL(orders.type, ''), "
+ + " IFNULL(orders.code, '') "
+ // +", "
+ // +" IFNULL(oi.name, '')"
+ + ")) LIKE :searchStr");
+ }
+ if(args.containsKey("custId")){
+ sql.append(" AND orders.custId = :custId ");
+ }
+ if(args.containsKey("sysGroupId")){
+ sql.append(" AND orders.sysGroupId = :sysGroupId ");
+ }
+ }
+ sql.append(" GROUP BY orders.id");
+ sql.append(" ORDER BY orders.date DESC");
+
+ if (args != null) {
+ if (args.containsKey("start") && args.containsKey("limit"))
+ sql.append(" LIMIT :start, :limit ");
+ }
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public Map loadOrder(Map args) {
+
+ StringBuilder sql = new StringBuilder("SELECT"
+ + " concat(orders.type,orders.code) AS orderNo,"
+ + " orders.id,"
+ + " orders.type,"
+ + " orders.code,"
+ + " orders.custId,"
+ + " orders.remarks,"
+ + " orders.status,"
+ + " orders.date,"
+ + " orders.ref,"
+ + " orders.refNo,"
+ + " orders.targetCompleteDate,"
+ + " CONCAT(IF(cu.firstName IS NULL, '', CONCAT(cu.firstName, ' ')),"
+ + " IF(cu.surname IS NULL, '', CONCAT(cu.surname, ' ')),"
+ + " IF(cu.nameCh IS NULL, '', cu.nameCh)) AS customerName,"
+ + " orders.date AS orderDate"
+ + " FROM"
+ + " orders "
+ + " LEFT JOIN customer cu ON orders.custId = cu.id "
+ + " WHERE orders.deleted = 0 AND orders.id =:id");
+
+ if(args.containsKey("sysGroupId")){
+ sql.append(" AND orders.sysGroupId = :sysGroupId ");
+ }
+
+ return jdbcDao.queryForMap(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> searchItems(Map args) {
+
+ StringBuilder sql = new StringBuilder(" SELECT"
+ + " oi.id,"
+ + " oi.orderId,"
+ + " oi.name,"
+ + " oi.price"
+ + " FROM order_item oi"
+ + " WHERE oi.deleted = 0");
+
+ if (args != null) {
+ if (args.containsKey(Params.ID)) sql.append(" AND oi.orderId = :id");
+ }
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ //todo: remove start
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> searchDetail(Map args) {
+
+ StringBuilder sql = new StringBuilder("SELECT"
+ + " concat(orders.type,orders.code) AS orderNo,"
+ + " orders.refNo,"
+ + " CONCAT(IF(cu.firstName IS NULL, '', CONCAT(cu.firstName, ' ')),"
+ + " IF(cu.surname IS NULL, '', CONCAT(cu.surname, ' ')),"
+ + " IF(cu.nameCh IS NULL, '', cu.nameCh)) AS customerName,"
+ + " orders.targetCompleteDate,"
+ + " orders.date AS orderDate"
+ + " FROM"
+ + " orders "
+ + " LEFT JOIN customer cu ON orders.custId = cu.id "
+ + " WHERE orders.deleted = 0");
+ if (args != null) {
+ if (args.containsKey(Params.ID)) sql.append(" AND orders.id =:id");
+ }
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> comboSearch(Map args) {
+
+ StringBuilder sql = new StringBuilder(" SELECT "
+ + " orders.id,"
+ + " orders.type,"
+ + " orders.code,"
+ + " CONCAT(IF(cu.firstName IS NULL, '', CONCAT(cu.firstName, ' ')),"
+ + " IF(cu.surname IS NULL, '', CONCAT(cu.surname, ' ')),"
+ + " IF(cu.nameCh IS NULL, '', cu.nameCh)) AS customerName "
+ + " FROM orders"
+ + " LEFT JOIN customer cu ON cu.id = orders.custId"
+ + " WHERE orders.deleted = 0 ");
+
+ if (args != null) {
+ if (args.containsKey("searchStr")) {
+ sql.append(
+ " AND( "
+ +" (CONCAT(IFNULL(cu.firstName, ''), "
+ + " IFNULL(cu.surname, ''), "
+ + " IFNULL(cu.nameCh,''), "
+ + " IFNULL(orders.type, ''), "
+ + " IFNULL(orders.code, '') "
+ + ")) LIKE :searchStr "
+ +" OR "
+ +" (CONCAT(IFNULL(cu.surname, ''), "
+ + " IFNULL(cu.firstName, ''), "
+ + " IFNULL(cu.nameCh,''), "
+ + " IFNULL(orders.type, ''), "
+ + " IFNULL(orders.code, '') "
+ + ")) LIKE :searchStr "
+ +" )");
+ }
+ if(args.containsKey("orderId")){
+ sql.append(" AND orders.id = :orderId ");
+ }
+ if(args.containsKey("customerId")){
+ sql.append(" AND orders.custId = :customerId ");
+ }
+ }
+ sql.append(" GROUP BY orders.id");
+ sql.append(" ORDER BY orders.date DESC");
+
+ if (args != null) {
+ if (args.containsKey("start") && args.containsKey("limit"))
+ sql.append(" LIMIT :start, :limit ");
+ }
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> comboSearch(Integer customerId) {
+
+ StringBuilder sql = new StringBuilder("SELECT id, code FROM orders WHERE orders.deleted =0");
+
+ if (customerId != -1) {
+ sql.append(" AND orders.custId = :customerId");
+ }
+ return jdbcDao.queryForList(sql.toString(), MapUtils.toHashMap("customerId", customerId));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/tls/api/web/AuthenticationController.java b/src/main/java/com/ffii/tls/api/web/AuthenticationController.java
new file mode 100644
index 0000000..ead77a2
--- /dev/null
+++ b/src/main/java/com/ffii/tls/api/web/AuthenticationController.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.api.web;
+
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.web.AbstractController;
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.api.service.AuthenticationService;
+import com.ffii.tbms.user.service.UserService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.password.StandardPasswordEncoder;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+/**
+ * Authentication API Controller
+ *
+ * @author Patrick
+ */
+@Controller
+@RequestMapping(value = "/api")
+public class AuthenticationController extends AbstractController {
+
+ private static final StandardPasswordEncoder PASSWORD_ENCODER = new StandardPasswordEncoder();
+
+ @Autowired
+ private AuthenticationService authenticationService;
+
+ @Autowired
+ private UserService userService;
+
+ @RequestMapping(value = "/auth", method = { RequestMethod.GET, RequestMethod.POST })
+ public String auth(Model model, @RequestParam String deviceId, @RequestParam String username,
+ @RequestParam String password, HttpServletRequest request) throws Exception {
+
+ Map data = userService.findUserMap(MapUtils.toHashMap("username", username));
+
+ if (data != null) {
+ String encodedPassword = (String) data.get("password");
+
+ boolean matches = PASSWORD_ENCODER.matches(password, encodedPassword);
+
+ // if password matches
+ if (matches) {
+ int userId = NumberUtils.intValue(data.get("id"));
+ String token = authenticationService.createAccessToken(userId, deviceId);
+ model.addAttribute("token", token);
+ model.addAttribute("id", data.get("id"));
+ model.addAttribute("firstname", data.get("firstname"));
+ model.addAttribute("lastname", data.get("lastname"));
+ model.addAttribute("fullname", data.get("fullname"));
+ }
+ model.addAttribute(Params.SUCCESS, matches);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/tls/api/web/CustomerApiController.java b/src/main/java/com/ffii/tls/api/web/CustomerApiController.java
new file mode 100644
index 0000000..a8556ba
--- /dev/null
+++ b/src/main/java/com/ffii/tls/api/web/CustomerApiController.java
@@ -0,0 +1,203 @@
+package com.ffii.tbms.api.web;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+
+import com.ffii.core.utils.CriteriaUtils;
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.web.ServletRequestUtils;
+import com.ffii.core.web.AbstractController;
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.api.service.AuthenticationService;
+import com.ffii.tbms.api.service.CustomerApiService;
+import com.ffii.tbms.api.service.OrderApiService;
+import com.ffii.tbms.customer.Customer;
+import com.ffii.tbms.customer.service.CustomerService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Controller
+@RequestMapping(value = "api/customer/")
+/**
+ * OrderApiController
+ */
+public class CustomerApiController extends AbstractController {
+
+ @Autowired
+ AuthenticationService authenticationService;
+
+ @Autowired
+ CustomerApiService service;
+
+ @Autowired
+ OrderApiService orderApiService;
+
+
+ @RequestMapping(value = "save", method = { RequestMethod.GET, RequestMethod.POST })
+ public String saveCustomer(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request,
+ @RequestParam(defaultValue = "-1") Integer id) {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth(userData.get("id"),"CUSTOMER_MAINTAIN");
+ if (!haveAuth) {
+ model.addAllAttributes(authenticationService.getNoAuthFoundMap());
+ return JsonView.NAME;
+ }
+
+ Customer instance;
+ if (id > 0) {
+ instance = service.find(id);
+ logger.info("meeting found");
+ } else {
+ instance = new Customer();
+ instance.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
+ }
+
+ ServletRequestUtils.doBind(request, instance, null);
+ id = service.saveOrUpdate(instance);
+
+ logger.info("Meeting Save: "+request.getParameter("date"));
+
+ model.addAttribute(Params.ID, id);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "list", method = { RequestMethod.GET, RequestMethod.POST })
+ public String listCustomer(Model model, @RequestParam String deviceId, @RequestParam String token,
+ HttpServletRequest request) throws Exception {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth(userData.get("id"),"CUSTOMER_MAINTAIN");
+ if (!haveAuth) {
+ model.addAllAttributes(authenticationService.getNoAuthFoundMap());
+ return JsonView.NAME;
+ }
+
+ final Map args = new HashMap();
+ args.put("sysGroupId", userData.get("sysGroupId"));
+
+ CriteriaUtils.addInteger(request, args, Params.ID);
+ CriteriaUtils.addStringLike(request, args, "searchStr");
+
+ if(args.containsKey("searchStr") && args.get("searchStr").toString().length()>=2){
+ args.put("_searchStr", args.get("searchStr"));
+ CriteriaUtils.addString(request, args, "searchStr");
+ }else{
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ model.addAttribute(Params.RECORDS, null);
+ return JsonView.NAME;
+ }
+
+ logger.info("list: args: " + args);
+ final List> records = service.searchCustomerList(args);
+ logger.info("list: records: " + records);
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ model.addAttribute(Params.RECORDS, records);
+
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "load", method = { RequestMethod.GET, RequestMethod.POST })
+ public String loadCustomer(Model model, @RequestParam String deviceId, @RequestParam String token,
+ HttpServletRequest request) throws Exception {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth(userData.get("id"),"CUSTOMER_MAINTAIN");
+ if (!haveAuth) {
+ model.addAllAttributes(authenticationService.getNoAuthFoundMap());
+ return JsonView.NAME;
+ }
+
+ final Map args = new HashMap();
+
+ CriteriaUtils.addInteger(request, args, Params.ID);
+ CriteriaUtils.addInteger(request, args, "custId");
+
+ logger.info("load: args: " + args);
+
+ Map data = service.loadCustomer(args);
+ logger.info("load: data: " + data);
+
+ List> orderList = orderApiService.search(args);
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ model.addAttribute(Params.DATA, data);
+ model.addAttribute("orderList", orderList);
+
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "combo.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String getCustomerComboJson(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request) throws Exception {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ Map args = new HashMap();
+
+ CriteriaUtils.addInteger(request, args, Params.ID);
+ CriteriaUtils.addStringLike(request, args, Params.QUERY);
+ CriteriaUtils.addInteger(request, args, "deptId");
+ args.put("sysGroupId", userData.get("sysGroupId"));
+
+ if(args.containsKey("query") && args.get("query").toString().length()>=2){
+ args.put("_query", args.get("query"));
+ CriteriaUtils.addStringLike(request, args, "query");
+ List> records = service.searchForCombo(args);
+ model.addAttribute(Params.RECORDS, records);
+ }else if(args.containsKey("id")){
+ List> records = service.searchForCombo(args);
+ if(records != null && records.size()>0)
+ model.addAttribute(Params.DATA, records.get(0));
+ else
+ model.addAttribute(Params.DATA, null);
+ }else{
+ List> records = service.searchForCombo(args);
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ model.addAttribute(Params.RECORDS, records);
+ return JsonView.NAME;
+ }
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/tls/api/web/FileApiController.java b/src/main/java/com/ffii/tls/api/web/FileApiController.java
new file mode 100644
index 0000000..b7b175e
--- /dev/null
+++ b/src/main/java/com/ffii/tls/api/web/FileApiController.java
@@ -0,0 +1,437 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.api.web;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ffii.core.setting.service.SettingsService;
+import com.ffii.core.utils.FileUtils;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.StringUtils;
+import com.ffii.core.utils.web.ServletRequestUtils;
+import com.ffii.core.web.AbstractController;
+import com.ffii.core.web.view.AbstractView;
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.api.service.AuthenticationService;
+import com.ffii.tbms.api.service.OrderApiService;
+import com.ffii.tbms.file.File;
+import com.ffii.tbms.file.FileBlob;
+import com.ffii.tbms.file.FileRef;
+import com.ffii.tbms.file.FileRefType;
+import com.ffii.tbms.file.service.FileService;
+import com.ffii.tbms.log.LogUtils;
+import com.ffii.tbms.log.service.AuditLogService;
+import com.ffii.tbms.order.Order;
+import com.ffii.tbms.order.service.OrderService;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
+
+@Controller
+@RequestMapping(value = "/api/file")
+public class FileApiController extends AbstractController {
+
+ @Autowired
+ private AuthenticationService authenticationService;
+
+ @Autowired
+ OrderApiService orderApiService;
+
+ @Autowired
+ private FileService fileService;
+
+ @Autowired
+ private SettingsService settingsService;
+
+ @Autowired
+ private AuditLogService auditLogService;
+
+ private static final long MAX_FILE_UPLOAD_SIZE = 25 * 1024 * 1024; // 25 MB
+
+ private static final boolean OVERWRITE_SAME_FILENAME = false;
+
+ private static final int DEFAULT_UPLOAD_MAX_FILE_SIZE_MB = 50;
+
+ @RequestMapping(value = "/list.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String listJson(Model model, @RequestParam String deviceId, @RequestParam String token,
+ @RequestParam String refType, @RequestParam Integer refId) {
+
+ Map data = authenticationService.findUserByAccessToken(deviceId, token);
+ if (data == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ Map args = new HashMap();
+ args.put(Params.REF_TYPE, refType);
+ args.put(Params.REF_ID, refId);
+
+ List> records = fileService.searchFiles(args);
+
+ model.addAttribute(Params.RECORDS, records);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/measurement-sheet/ul", method = RequestMethod.POST)
+ public String uploadMeasurementSheetFile(HttpServletRequest request, Model model, @RequestParam String deviceId, @RequestParam String token,
+ @RequestParam int orderId, @RequestParam MultipartFile multipartFile) throws Exception {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ logger.info("upload file start");
+ Order order = orderApiService.find(orderId);
+ String refType = FileRefType.MEASUREMENT_SHEET;
+ String refCode = order.getCustId() + "";
+ Integer refId = orderId;
+
+ // get file upload max file size setting
+ int uploadMaxFileSize = settingsService.getInt("FILE.upload.maxFileSize", DEFAULT_UPLOAD_MAX_FILE_SIZE_MB) * 1024 * 1024;
+
+ Boolean success = Boolean.TRUE;
+
+ // only proceed if multipartFile is not null, and has file size
+ if (multipartFile != null && multipartFile.getSize() > 0 && multipartFile.getSize() <= uploadMaxFileSize) {
+
+ // DEBUG LOG
+ logger.info("multipartFile.getSize() = " + multipartFile.getSize());
+
+ File file = new File();
+ file.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
+ file.setFilename(multipartFile.getOriginalFilename());
+ // file.setMimetype(multipartFile.getContentType());
+ file.setMimetype(FileUtils.guessMimetype(file.getFilename()));
+ file.setFilesize(multipartFile.getSize());
+ file.setDescription(ServletRequestUtils.getTrimmedStringParameter(request, "description"));
+
+ FileBlob fileBlob = new FileBlob();
+ fileBlob.setBytes(multipartFile.getBytes());
+ fileBlob.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
+
+ FileRef fileRef = new FileRef();
+ fileRef.setRefId(refId);
+ fileRef.setRefType(refType);
+ fileRef.setRefCode(refCode);
+ fileRef.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
+
+ // get height and width if mimetype is png or jpeg
+ if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype()) || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) {
+ BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
+ if (image != null) {
+ file.setImageHeight(image.getHeight());
+ file.setImageWidth(image.getWidth());
+ }
+ }
+
+ if (OVERWRITE_SAME_FILENAME) {
+ // search for existing file(s) with the same refType, refId, and filename
+ List> existingFiles = fileService.searchFiles(
+ MapUtils.toHashMap("refType", refType, "refId", refId, "filename", file.getFilename()));
+
+ // delete them if found
+ for (Map existingFile : existingFiles) {
+ fileService.deleteFile((Integer) existingFile.get("id"), (Integer) existingFile.get("refId"),
+ (String) existingFile.get("refType"), (String) existingFile.get("skey"));
+ }
+ }
+
+ // create UserFile
+ file.setSkey(RandomStringUtils.randomAlphanumeric(16));
+ fileService.saveFile(file);
+
+ // create UserFileBlob
+ fileBlob.setFileId(file.getId());
+ fileService.saveFileBlob(fileBlob);
+
+ // create UserFileRef
+ fileRef.setFileId(file.getId());
+ fileService.saveFileRef(fileRef);
+
+ order.setFileId(file.getId());
+ orderApiService.saveOrUpdate(order);
+ } else {
+ success = Boolean.FALSE;
+
+ // if not success, return msg to client
+ model.addAttribute(Params.MSG, getMessageSourceAccessor().getMessage("Upload Failed"));
+ }
+
+ model.addAttribute(Params.SUCCESS, success);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/ul", method = RequestMethod.POST)
+ public String uploadFile(HttpServletRequest request, Model model, @RequestParam String deviceId,
+ @RequestParam String token, @RequestParam int refId, @RequestParam String refType,
+ @RequestParam(defaultValue = StringUtils.EMPTY) String refCode, @RequestParam MultipartFile multipartFile)
+ throws IOException {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ // only proceed if multipartFile is not null, and has file size
+ if(multipartFile == null || multipartFile.getSize() <= 0 || multipartFile.getSize() > MAX_FILE_UPLOAD_SIZE){
+ model.addAttribute(Params.MSG, getMessageSourceAccessor().getMessage("FILE.msg.uploadFailed"));
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ return JsonView.NAME;
+ }
+
+ // DEBUG LOG
+ logger.info("multipartFile.getSize() = " + multipartFile.getSize());
+
+ File file = new File();
+ file.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
+ file.setFilename(multipartFile.getOriginalFilename());
+ // file.setMimetype(multipartFile.getContentType());
+ file.setMimetype(FileUtils.guessMimetype(file.getFilename()));
+ file.setFilesize(multipartFile.getSize());
+
+ FileBlob fileBlob = new FileBlob();
+ fileBlob.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
+ fileBlob.setBytes(multipartFile.getBytes());
+
+ FileRef fileRef = new FileRef();
+ fileRef.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
+ fileRef.setRefId(refId);
+ fileRef.setRefType(refType);
+ fileRef.setRefCode(refCode);
+
+ // get height and width if mimetype is png or jpeg
+ if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype())
+ || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) {
+ BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
+ if (image != null) {
+ file.setImageHeight(image.getHeight());
+ file.setImageWidth(image.getWidth());
+ }
+ }
+
+ // search for existing file(s) with the same refType, refId, and filename
+ List> existingFiles = fileService.searchFiles(
+ MapUtils.toHashMap("refType", refType, "refId", refId, "filename", file.getFilename()));
+
+ // delete them if found
+ for (Map existingFile : existingFiles) {
+ fileService.deleteFile((Integer) existingFile.get("id"), (Integer) existingFile.get("refId"),
+ (String) existingFile.get("refType"), (String) existingFile.get("skey"));
+ }
+
+ // create UserFile
+ file.setSkey(RandomStringUtils.randomAlphanumeric(16));
+ fileService.saveFile(file);
+
+ // create UserFileBlob
+ fileBlob.setFileId(file.getId());
+ fileService.saveFileBlob(fileBlob);
+
+ // create UserFileRef
+ fileRef.setFileId(file.getId());
+ fileService.saveFileRef(fileRef);
+
+ Map map = new HashMap<>();
+ map.put("skey", file.getSkey());
+ map.put("fileId", file.getId());
+ map.put("filename", file.getFilename());
+ map.put("filesize", file.getFilesize());
+ map.put("refType", fileRef.getRefType());
+ map.put("refId", fileRef.getRefId());
+ map.put("mimetype", file.getMimetype());
+
+ model.addAttribute(Params.DATA, map);
+
+ if(refType.equals(FileRefType.ORDER)){
+ ObjectMapper mapper = new ObjectMapper();
+ auditLogService.save("orders",
+ refId,
+ NumberUtils.intValue(userData.get("id")),
+ new Date(),
+ LogUtils.ACTION_CREATE,
+ mapper.writeValueAsString(MapUtils.toHashMap("photoFileId", file.getId(), "filename",file.getFilename())));
+ }
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/dl/{id}/{skey}/{filename}", method = RequestMethod.GET)
+ public void download(HttpServletResponse response, Model model, @RequestParam(defaultValue = "false") boolean dl,
+ @PathVariable int id, @PathVariable String skey) throws IOException {
+
+ File file = fileService.findFileByIdAndKey(id, skey);
+
+ if (file != null) {
+ FileBlob fileBlob = fileService.findFileBlobByFileId(id);
+
+ response.reset();
+ response.setContentType(file.getMimetype());
+ response.setContentLength((int) file.getFilesize());
+ response.setHeader("Content-Transfer-Encoding", "binary");
+ response.setHeader("Content-Disposition", String.format("%s; filename=\"%s\"", dl ? "attachment" : "inline",
+ response.encodeURL(file.getFilename())));
+
+ ServletOutputStream out = response.getOutputStream();
+
+ try {
+ out.write(fileBlob.getBytes());
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ logger.warn(e.getMessage());
+ } finally {
+ out.close();
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ }
+ } else {
+ logger.info("*** 400 BAD REQUEST ***");
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ }
+
+ @RequestMapping(value = "/delete", method = RequestMethod.POST)
+ public String delete(HttpServletResponse response, Model model, @RequestParam String deviceId,
+ @RequestParam String token, @RequestParam Integer fileId, @RequestParam Integer refId,
+ @RequestParam String refType, @RequestParam String skey) throws IOException {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ fileService.deleteFile(fileId, refId, refType, skey);
+
+ if(refType.equals(FileRefType.MEASUREMENT_SHEET)){
+ ObjectMapper mapper = new ObjectMapper();
+ auditLogService.save("orders",
+ refId,
+ NumberUtils.intValue(userData.get("id")),
+ new Date(),
+ LogUtils.ACTION_DELETE,
+ mapper.writeValueAsString(MapUtils.toHashMap("fileId", fileId)));
+ }else if(refType.equals(FileRefType.ORDER)){
+ ObjectMapper mapper = new ObjectMapper();
+ auditLogService.save("orders",
+ refId,
+ NumberUtils.intValue(userData.get("id")),
+ new Date(),
+ LogUtils.ACTION_DELETE,
+ mapper.writeValueAsString(MapUtils.toHashMap("photoFileId", fileId)));
+ }
+
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/thumbnail/{id}/{skey}/{filename}", method = RequestMethod.GET)
+ public void thumbnail(HttpServletResponse response, Model model, @RequestParam(defaultValue = "false") boolean dl,
+ @PathVariable int id, @PathVariable String skey) throws IOException {
+
+ File file = fileService.findFileByIdAndKey(id, skey);
+
+ if (file != null) {
+ FileBlob fileBlob = fileService.findFileBlobByFileId(id);
+
+ response.reset();
+ response.setContentType(file.getMimetype());
+ // response.setContentLength((int) file.getFilesize());
+ response.setHeader("Content-Transfer-Encoding", "binary");
+ response.setHeader("Content-Disposition", String.format("%s; filename=\"%s\"", dl ? "attachment" : "inline",
+ response.encodeURL(file.getFilename())));
+
+ int limit = 500;
+ BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
+ int width = image.getWidth();
+ int height = image.getHeight();
+ if (width > height) {
+ if (width > limit) {
+ height = height * limit / width;
+ width = limit;
+ }
+ } else {
+ if (height > limit) {
+ width = width * limit / height;
+ height = limit;
+ }
+ }
+ image = scale(image, width, height);
+ ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+ ImageIO.write(image, "jpg", tmp);
+ tmp.close();
+ response.setContentLength((int) tmp.size());
+
+ ServletOutputStream out = response.getOutputStream();
+
+ try {
+ out.write(tmp.toByteArray());
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ logger.warn(e.getMessage());
+ } finally {
+ out.close();
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ }
+ } else {
+ logger.info("*** 400 BAD REQUEST ***");
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ }
+
+ static BufferedImage scale(BufferedImage originalImage, int w, int h) {
+ BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+ int x, y;
+ int ww = originalImage.getWidth();
+ int hh = originalImage.getHeight();
+ for (x = 0; x < w; x++) {
+ for (y = 0; y < h; y++) {
+ int col = originalImage.getRGB(x * ww / w, y * hh / h);
+ img.setRGB(x, y, col);
+ }
+ }
+ return img;
+ }
+
+
+}
diff --git a/src/main/java/com/ffii/tls/api/web/MainApiController.java b/src/main/java/com/ffii/tls/api/web/MainApiController.java
new file mode 100644
index 0000000..87b00a9
--- /dev/null
+++ b/src/main/java/com/ffii/tls/api/web/MainApiController.java
@@ -0,0 +1,75 @@
+package com.ffii.tbms.api.web;
+
+import java.io.IOException;
+import java.util.Map;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import com.ffii.core.utils.Params;
+import com.ffii.core.web.AbstractController;
+import com.ffii.tbms.api.service.AuthenticationService;
+import com.ffii.tbms.file.File;
+import com.ffii.tbms.file.FileBlob;
+import com.ffii.tbms.file.service.FileService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Controller
+@RequestMapping(value = "api/")
+
+public class MainApiController extends AbstractController {
+
+ @Autowired
+ AuthenticationService authenticationService;
+
+ @Autowired
+ private FileService fileService;
+
+
+ @RequestMapping(value = "/dl/{id}/{skey}/{filename}", method = { RequestMethod.GET, RequestMethod.POST })
+ public void download(HttpServletResponse response, @RequestParam String deviceId, @RequestParam String token, Model model,
+ @RequestParam(defaultValue = "false") boolean dl,
+ @PathVariable int id, @PathVariable String skey) throws IOException {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return;
+ }
+
+ File file = fileService.findFileByIdAndKey(id, skey);
+
+ if (file != null) {
+ FileBlob fileBlob = fileService.findFileBlobByFileId(id);
+
+ response.reset();
+ response.setContentType(file.getMimetype());
+ response.setContentLength((int) file.getFilesize());
+ response.setHeader("Content-Transfer-Encoding", "binary");
+ response.setHeader("Content-Disposition", String.format("%s; filename=\"%s\"", dl ? "attachment" : "inline",
+ response.encodeURL(file.getFilename())));
+
+ ServletOutputStream out = response.getOutputStream();
+
+ try {
+ out.write(fileBlob.getBytes());
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ logger.warn(e.getMessage());
+ } finally {
+ out.close();
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ }
+ } else {
+ logger.info("*** 400 BAD REQUEST ***");
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/tls/api/web/MeetingApiController.java b/src/main/java/com/ffii/tls/api/web/MeetingApiController.java
new file mode 100644
index 0000000..b76debe
--- /dev/null
+++ b/src/main/java/com/ffii/tls/api/web/MeetingApiController.java
@@ -0,0 +1,247 @@
+package com.ffii.tbms.api.web;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.ffii.core.utils.CriteriaUtils;
+import com.ffii.core.utils.DateUtils;
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.web.ServletRequestUtils;
+import com.ffii.core.web.AbstractController;
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.api.service.AuthenticationService;
+import com.ffii.tbms.api.service.MeetingApiService;
+import com.ffii.tbms.meeting.Meeting;
+import com.ffii.tbms.meeting.service.MeetingService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Controller
+@RequestMapping(value = "api/meeting/")
+/**
+ * OrderApiController
+ */
+public class MeetingApiController extends AbstractController {
+
+ @Autowired
+ MeetingApiService service;
+
+ @Autowired
+ AuthenticationService authenticationService;
+
+ @Autowired
+ private MeetingService meetingService;
+
+
+ @RequestMapping(value = "list.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String getMeetingJson(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request) throws ServletRequestBindingException {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAttribute(Params.AUTH, Boolean.FALSE);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ return JsonView.NAME;
+ }
+
+ /*
+ Calendar formMonthCalendar = Calendar.getInstance();
+ formMonthCalendar.setTime(DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH));
+ formMonthCalendar.set(Calendar.DAY_OF_MONTH, 1);
+ formMonthCalendar.set(Calendar.MONTH, fromMonth);
+ formMonthCalendar.set(Calendar.YEAR, year);
+
+ Calendar toMonthCalendar = Calendar.getInstance();
+ toMonthCalendar.setTime(DateUtils.truncate(new Date(), Calendar.DAY_OF_MONTH));
+ toMonthCalendar.set(Calendar.DAY_OF_MONTH, 1);
+ toMonthCalendar.set(Calendar.MONTH, toMonth);
+ toMonthCalendar.set(Calendar.YEAR, year);
+ */
+
+ Map args = new HashMap();
+ CriteriaUtils.addDate(request, args, "fromDate");
+ CriteriaUtils.addDate(request, args, "toDate");
+
+ //toDo remove
+ CriteriaUtils.addInteger(request, args, "fromMonth");
+ CriteriaUtils.addInteger(request, args, "toMonth");
+ CriteriaUtils.addInteger(request, args, "year");
+
+ Date firstDateOfMonth=null, lastDateOfMonth=null;
+
+ logger.info("args:"+args);
+
+ if(args.containsKey("fromDate") && args.containsKey("toDate")){
+ firstDateOfMonth = (Date)args.get("fromDate");
+ lastDateOfMonth = (Date)args.get("toDate");
+ }else if(args.containsKey("fromMonth") && args.containsKey("toMonth") && args.containsKey("year")){
+ logger.info("fromMonth:"+args.get("fromMonth")+", toMonth:"+args.get("toMonth")+", year:"+args.get("year"));
+ logger.info("year:"+Integer.parseInt(args.get("year").toString()));
+ logger.info("fromMonth:"+Integer.parseInt(args.get("fromMonth").toString()));
+ firstDateOfMonth = DateUtils.getFirstDateOfMonthByYearAndMonth(Integer.parseInt(args.get("year").toString()), Integer.parseInt(args.get("fromMonth").toString()));
+ lastDateOfMonth = DateUtils.getLastDateOfMonthByYearAndMonth(Integer.parseInt(args.get("year").toString()), Integer.parseInt(args.get("toMonth").toString()));
+ }else{
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.DATA, new HashMap<>());
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ //todo remove
+ model.addAttribute(Params.RECORDS, new ArrayList<>());
+
+ return JsonView.NAME;
+ }
+
+ logger.info(firstDateOfMonth.getTime());
+ logger.info(lastDateOfMonth.getTime());
+
+ List> records = new ArrayList<>();
+ Map dayMap = new HashMap<>();
+ while (DateUtils.isEarlierThan(firstDateOfMonth, lastDateOfMonth)) {
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ String dateStr = dateFormat.format(firstDateOfMonth);
+ List> events = service.searchMeeting(dateStr);
+ dayMap.put(dateStr, events);
+ firstDateOfMonth = DateUtils.addDays(firstDateOfMonth, 1);
+ }
+
+ DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+ String dateStr = dateFormat.format(lastDateOfMonth);
+ List> events = service.searchMeeting(dateStr);
+ dayMap.put(dateStr, events);
+
+ records.add(dayMap);
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.DATA, dayMap);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ //todo remove
+ model.addAttribute(Params.RECORDS, records);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "selected-list.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String getMeetingSelectedJson(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request,
+ @RequestParam Date selectedDate) throws ServletRequestBindingException {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAttribute(Params.AUTH, Boolean.FALSE);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ return JsonView.NAME;
+ }
+
+ Map args = new HashMap<>();
+ CriteriaUtils.addDate(request, args, "selectedDate");
+
+ List> searchMeetingByDate = service.searchMeetingByDate(args);
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.RECORDS, searchMeetingByDate);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "load", method = { RequestMethod.GET, RequestMethod.POST })
+ public String loadMeeting(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request,
+ @RequestParam int id) throws ServletRequestBindingException {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAttribute(Params.AUTH, Boolean.FALSE);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ return JsonView.NAME;
+ }
+
+ Map args = new HashMap<>();
+ args.put("id", id);
+
+ Map data = service.loadMeeting(args);
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.DATA, data);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "save", method = { RequestMethod.GET, RequestMethod.POST })
+ public String saveMeeting(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request,
+ @RequestParam(defaultValue = "-1") Integer id) {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAttribute(Params.AUTH, Boolean.FALSE);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth( userData.get("id"), "MEETING_MAINTAIN");
+ if (!haveAuth) {
+ model.addAttribute(Params.AUTH, Boolean.FALSE);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ return JsonView.NAME;
+ }
+
+ Meeting instance;
+ if (id > 0) {
+ instance = meetingService.find(id);
+ logger.info("meeting found");
+ } else {
+ instance = new Meeting();
+ instance.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
+ }
+
+ ServletRequestUtils.doBind(request, instance, null);
+ id = meetingService.saveOrUpdate(instance);
+
+ logger.info("Meeting Save: "+request.getParameter("date"));
+
+ model.addAttribute(Params.ID, id);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "delete", method = { RequestMethod.GET, RequestMethod.POST })
+ public String deleteMeeting(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request,
+ @RequestParam Integer id) {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAttribute(Params.AUTH, Boolean.FALSE);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth( userData.get("id"), "MEETING_MAINTAIN");
+ if (!haveAuth) {
+ model.addAttribute(Params.AUTH, Boolean.FALSE);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ return JsonView.NAME;
+ }
+
+ Meeting instance = meetingService.find(id);
+ instance.setDeleted(true);
+ meetingService.saveOrUpdate(instance);
+
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/tls/api/web/OrderApiController.java b/src/main/java/com/ffii/tls/api/web/OrderApiController.java
new file mode 100644
index 0000000..0f72b64
--- /dev/null
+++ b/src/main/java/com/ffii/tls/api/web/OrderApiController.java
@@ -0,0 +1,386 @@
+package com.ffii.tbms.api.web;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.ffii.core.utils.CriteriaUtils;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.web.ServletRequestUtils;
+import com.ffii.core.web.AbstractController;
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.api.service.AuthenticationService;
+import com.ffii.tbms.api.service.CustomerApiService;
+import com.ffii.tbms.api.service.OrderApiService;
+import com.ffii.tbms.customer.service.CustomerService;
+import com.ffii.tbms.file.FileRefType;
+import com.ffii.tbms.file.service.FileService;
+import com.ffii.tbms.log.LogUtils;
+import com.ffii.tbms.order.Order;
+import com.ffii.tbms.order.OrderItem;
+import com.ffii.tbms.order.service.OrderItemService;
+import com.ffii.tbms.order.service.OrderService;
+
+import org.apache.commons.lang3.SerializationUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Controller
+@RequestMapping(value = "api/order/")
+/**
+ * OrderApiController
+ */
+public class OrderApiController extends AbstractController {
+
+ @Autowired
+ AuthenticationService authenticationService;
+
+ @Autowired
+ OrderApiService service;
+
+ @Autowired
+ OrderItemService orderItemService;
+
+ @Autowired
+ CustomerApiService customerApiService;
+
+ @Autowired
+ private FileService fileService;
+
+ @RequestMapping(value = "save", method = { RequestMethod.GET, RequestMethod.POST })
+ public String saveOrder(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request,
+ @RequestParam(defaultValue = "-1") Integer id) {
+
+ logger.info("saveOrder : "+id);
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth(userData.get("id"),"ORDER_MAINTAIN");
+ if (!haveAuth) {
+ model.addAllAttributes(authenticationService.getNoAuthFoundMap());
+ return JsonView.NAME;
+ }
+
+ Order instance;
+ if (id > 0) {
+ instance = service.find(id);
+ logger.info("order found : "+id);
+ } else {
+ instance = new Order();
+ instance.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
+ logger.info("new order: "+NumberUtils.toInt(userData.get("sysGroupId"),0));
+ }
+
+ ServletRequestUtils.doBind(request, instance, null);
+ id = service.saveOrUpdate(instance);
+ logger.info("Order Save: "+instance.toString());
+
+ model.addAttribute(Params.ID, id);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "list", method = { RequestMethod.GET, RequestMethod.POST })
+ public String listOrder(Model model, @RequestParam String deviceId, @RequestParam String token,
+ HttpServletRequest request) throws Exception {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth(userData.get("id"),"ORDER_MAINTAIN");
+ if (!haveAuth) {
+ model.addAllAttributes(authenticationService.getNoAuthFoundMap());
+ return JsonView.NAME;
+ }
+
+ final Map args = new HashMap();
+
+ CriteriaUtils.addInteger(request, args, Params.ID);
+ CriteriaUtils.addStringLike(request, args, "searchStr");
+ CriteriaUtils.addInteger(request, args, "custId");
+ args.put("sysGroupId", userData.get("sysGroupId"));
+
+ CriteriaUtils.addInteger(request, args, "start");
+ CriteriaUtils.addInteger(request, args, "limit");
+
+ logger.info("args : " + args);
+
+ final List> records = service.search(args);
+
+ //logger.info("record : " + records);
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ model.addAttribute(Params.RECORDS, records);
+
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "combo", method = { RequestMethod.GET, RequestMethod.POST })
+ public String OrderCombo(Model model, @RequestParam String deviceId, @RequestParam String token,
+ HttpServletRequest request) throws ServletRequestBindingException {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ final Map args = new HashMap();
+ CriteriaUtils.addInteger(request, args, Params.ID);
+ CriteriaUtils.addInteger(request, args, "customerId");
+ CriteriaUtils.addStringLike(request, args, "searchStr");
+
+ if(args.containsKey("searchStr") && args.get("searchStr").toString().length()>=2){
+ args.put("_searchStr", args.get("searchStr"));
+ CriteriaUtils.addString(request, args, "searchStr");
+ List> records = service.comboSearch(args);
+ model.addAttribute(Params.RECORDS, records);
+ }else if(args.containsKey("id")){
+ List> records = service.comboSearch(args);
+ if(records != null)
+ model.addAttribute(Params.DATA, records.get(0));
+ else
+ model.addAttribute(Params.DATA, new HashMap<>());
+ }else if(args.containsKey("customerId")){
+ List> records = service.comboSearch(NumberUtils.intValue(args.get("customerId")));
+ if(records != null)
+ model.addAttribute(Params.DATA, records.get(0));
+ else
+ model.addAttribute(Params.DATA, new HashMap<>());
+ }else{
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ model.addAttribute(Params.RECORDS, null);
+ return JsonView.NAME;
+ }
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "detail", method = { RequestMethod.GET, RequestMethod.POST })
+ public String loadDetail(Model model, @RequestParam String deviceId, @RequestParam String token,
+ HttpServletRequest request) throws Exception {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth(userData.get("id"),"ORDER_MAINTAIN");
+ if (!haveAuth) {
+ model.addAllAttributes(authenticationService.getNoAuthFoundMap());
+ return JsonView.NAME;
+ }
+
+ final Map args = new HashMap<>();
+
+ CriteriaUtils.addInteger(request, args, Params.ID);
+
+ final Map data = service.loadOrder(args);
+ final List> itemRecords = service.searchItems(args);
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ model.addAttribute(Params.DATA, data);
+ model.addAttribute("itemRecords", itemRecords);
+
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "load", method = { RequestMethod.GET, RequestMethod.POST })
+ public String loadForOrderEdit(Model model, @RequestParam String deviceId, @RequestParam String token,
+ HttpServletRequest request) throws Exception {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth(userData.get("id"),"ORDER_MAINTAIN");
+ if (!haveAuth) {
+ model.addAllAttributes(authenticationService.getNoAuthFoundMap());
+ return JsonView.NAME;
+ }
+
+ final Map args = new HashMap<>();
+
+ CriteriaUtils.addInteger(request, args, Params.ID);
+
+ final Map data = service.loadOrder(args);
+
+ model.addAttribute("selectedOrderRef", null);
+ if(data.get("ref") != null){
+ List> refOrder = service.comboSearch(MapUtils.toHashMap("sysGroupId", userData.get("sysGroupId"),"orderId", data.get("ref")));
+ if(refOrder!=null && refOrder.size()>0){
+ model.addAttribute("selectedOrderRef", refOrder.get(0));
+ }
+ }
+
+ model.addAttribute("selectedCustomer", null);
+ if(data.get("custId") != null){
+ List> cust = customerApiService.searchForCombo(MapUtils.toHashMap("id", data.get("custId")));
+ if(cust!=null && cust.size()>0){
+ model.addAttribute("selectedCustomer", cust.get(0));
+ }
+ }
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ model.addAttribute(Params.DATA, data);
+
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "get-current-ms.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String getCurrentMS(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request, Integer orderId) {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ Order order = service.find(orderId);
+
+ Map args = new HashMap();
+ List> records = new ArrayList<>();
+ if (order != null && order.getFileId() != null && order.getFileId() > 0) {
+ args.put("fileId", order.getFileId());
+ args.put("refType", FileRefType.MEASUREMENT_SHEET);
+ records = fileService.searchFiles(args);
+ }
+ model.addAttribute(Params.RECORDS, records);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "image-list-json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String getImageJson(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request, Integer orderId) {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ final Map args = new HashMap();
+ args.put("refType", FileRefType.ORDER);
+ args.put("refId", orderId);
+ final List> records = fileService.searchFiles(args);
+
+ model.addAttribute(Params.AUTH, Boolean.TRUE);
+ model.addAttribute(Params.RECORDS, records);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "item/save", method = { RequestMethod.GET, RequestMethod.POST })
+ public String saveOrderItem(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request,
+ @RequestParam(defaultValue = "-1") Integer id) throws ServletRequestBindingException, JsonProcessingException {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth(userData.get("id"),"ORDER_MAINTAIN");
+ if (!haveAuth) {
+ model.addAllAttributes(authenticationService.getNoAuthFoundMap());
+ return JsonView.NAME;
+ }
+
+ OrderItem instance;
+ OrderItem oldRecord;
+ if (id > 0) {
+ instance = orderItemService.find(id);
+ logger.info("order Item found : "+id);
+ orderItemService.compare(instance, request);
+ oldRecord = SerializationUtils.clone(instance);
+ } else {
+ instance = new OrderItem();
+ oldRecord = new OrderItem();
+ instance.setSysGroupId(NumberUtils.toInt(userData.get("sysGroupId"),0));
+ logger.info("new order Item");
+ }
+
+ ServletRequestUtils.doBind(request, instance, null);
+ id = orderItemService.saveOrUpdate(instance);
+ logger.info("Order Item Save: "+instance.toString());
+
+ //save log
+ if(oldRecord.getId() != null){
+ orderItemService.log(LogUtils.ACTION_UPDATE, id, NumberUtils.intValue(userData.get("id")), instance, oldRecord);
+ }else{
+ orderItemService.log(LogUtils.ACTION_CREATE, id, NumberUtils.intValue(userData.get("id")), instance);
+ }
+
+ model.addAttribute(Params.ID, id);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "item/delete", method = { RequestMethod.GET, RequestMethod.POST })
+ public String deleteOrderItem(Model model, @RequestParam String deviceId, @RequestParam String token, HttpServletRequest request,
+ @RequestParam(defaultValue = "-1") Integer id) throws JsonProcessingException {
+
+ Map userData = authenticationService.findUserByAccessToken(deviceId, token);
+ if (userData == null) {
+ model.addAllAttributes(authenticationService.getNoUserFoundMap());
+ return JsonView.NAME;
+ }
+
+ boolean haveAuth = authenticationService.haveAuth(userData.get("id"),"ORDER_MAINTAIN");
+ if (!haveAuth) {
+ model.addAllAttributes(authenticationService.getNoAuthFoundMap());
+ return JsonView.NAME;
+ }
+
+ OrderItem instance;
+ if (id > 0) {
+ instance = orderItemService.find(id);
+ logger.info("order Item found (delete) : "+id);
+ instance.setDeleted(true);
+ id = orderItemService.saveOrUpdate(instance);
+ orderItemService.log("Delete", id, NumberUtils.intValue(userData.get("id")), instance);
+
+ model.addAttribute(Params.ID, id);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ return JsonView.NAME;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/tls/common/MaterialBand.java b/src/main/java/com/ffii/tls/common/MaterialBand.java
new file mode 100644
index 0000000..6c6e615
--- /dev/null
+++ b/src/main/java/com/ffii/tls/common/MaterialBand.java
@@ -0,0 +1,51 @@
+package com.ffii.tbms.common;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import com.ffii.core.BaseEntity;
+
+@Entity
+@Table(name = "material_band")
+public class MaterialBand extends BaseEntity {
+
+ private static final long serialVersionUID = 8039256623625569162L;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String name;
+
+ @Column(columnDefinition = "int(11)")
+ private Integer fileId;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String expectedPriceFormula;
+
+ public MaterialBand() {
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getFileId() {
+ return fileId;
+ }
+
+ public void setFileId(Integer fileId) {
+ this.fileId = fileId;
+ }
+
+ public String getExpectedPriceFormula() {
+ return expectedPriceFormula;
+ }
+
+ public void setExpectedPriceFormula(String expectedPriceFormula) {
+ this.expectedPriceFormula = expectedPriceFormula;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/common/MaterialItem.java b/src/main/java/com/ffii/tls/common/MaterialItem.java
new file mode 100644
index 0000000..4c6317c
--- /dev/null
+++ b/src/main/java/com/ffii/tls/common/MaterialItem.java
@@ -0,0 +1,151 @@
+package com.ffii.tbms.common;
+
+import java.math.BigDecimal;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import com.ffii.core.BaseEntity;
+
+@Entity
+@Table(name = "material_item")
+public class MaterialItem extends BaseEntity {
+
+ private static final long serialVersionUID = -483809080806589852L;
+
+ @Column(columnDefinition = "int(11)")
+ private Integer bandId;
+
+ @Column(columnDefinition = "int(11)")
+ private Integer orderIdx;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String name;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String bunch;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String itemNo;
+
+ @Column(columnDefinition = "decimal(18,4)")
+ private BigDecimal cost;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String fabricNum;
+
+ @Column(columnDefinition = "decimal(18,4)")
+ private BigDecimal suitPrice;
+
+ @Column(columnDefinition = "decimal(18,4)")
+ private BigDecimal jacketPrice;
+
+ @Column(columnDefinition = "decimal(18,4)")
+ private BigDecimal overcoatPrice;
+
+ @Column(columnDefinition = "decimal(18,4)")
+ private BigDecimal pantsPrice;
+
+ @Column(columnDefinition = "decimal(18,4)")
+ private BigDecimal otherPrice;
+
+ public MaterialItem(){}
+
+ public Integer getBandId() {
+ return bandId;
+ }
+
+ public void setBandId(Integer bandId) {
+ this.bandId = bandId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getBunch() {
+ return bunch;
+ }
+
+ public void setBunch(String bunch) {
+ this.bunch = bunch;
+ }
+
+ public String getItemNo() {
+ return itemNo;
+ }
+
+ public void setItemNo(String itemNo) {
+ this.itemNo = itemNo;
+ }
+
+ public BigDecimal getCost() {
+ return cost;
+ }
+
+ public void setCost(BigDecimal cost) {
+ this.cost = cost;
+ }
+
+ public BigDecimal getSuitPrice() {
+ return suitPrice;
+ }
+
+ public void setSuitPrice(BigDecimal suitPrice) {
+ this.suitPrice = suitPrice;
+ }
+
+ public BigDecimal getJacketPrice() {
+ return jacketPrice;
+ }
+
+ public void setJacketPrice(BigDecimal jacketPrice) {
+ this.jacketPrice = jacketPrice;
+ }
+
+ public BigDecimal getOvercoatPrice() {
+ return overcoatPrice;
+ }
+
+ public void setOvercoatPrice(BigDecimal overcoatPrice) {
+ this.overcoatPrice = overcoatPrice;
+ }
+
+ public BigDecimal getPantsPrice() {
+ return pantsPrice;
+ }
+
+ public void setPantsPrice(BigDecimal pantsPrice) {
+ this.pantsPrice = pantsPrice;
+ }
+
+ public BigDecimal getOtherPrice() {
+ return otherPrice;
+ }
+
+ public void setOtherPrice(BigDecimal otherPrice) {
+ this.otherPrice = otherPrice;
+ }
+
+ public String getFabricNum() {
+ return fabricNum;
+ }
+
+ public void setFabricNum(String fabricNum) {
+ this.fabricNum = fabricNum;
+ }
+
+ public Integer getOrderIdx() {
+ return orderIdx;
+ }
+
+ public void setOrderIdx(Integer orderIdx) {
+ this.orderIdx = orderIdx;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/common/dao/MaterialBandDao.java b/src/main/java/com/ffii/tls/common/dao/MaterialBandDao.java
new file mode 100644
index 0000000..381bb0c
--- /dev/null
+++ b/src/main/java/com/ffii/tls/common/dao/MaterialBandDao.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright 2013 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the PCS project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.common.dao;
+
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import com.ffii.core.dao.HibernateDao;
+import com.ffii.tbms.common.MaterialBand;
+
+@Repository
+public class MaterialBandDao extends HibernateDao {
+
+ @Autowired
+ public MaterialBandDao(SessionFactory sessionFactory) {
+ super(MaterialBand.class);
+ setSessionFactory(sessionFactory);
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/common/dao/MaterialItemDao.java b/src/main/java/com/ffii/tls/common/dao/MaterialItemDao.java
new file mode 100644
index 0000000..fe569d9
--- /dev/null
+++ b/src/main/java/com/ffii/tls/common/dao/MaterialItemDao.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright 2013 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the PCS project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.common.dao;
+
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import com.ffii.core.dao.HibernateDao;
+import com.ffii.tbms.common.MaterialItem;
+
+@Repository
+public class MaterialItemDao extends HibernateDao {
+
+ @Autowired
+ public MaterialItemDao(SessionFactory sessionFactory) {
+ super(MaterialItem.class);
+ setSessionFactory(sessionFactory);
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/common/service/MaterialService.java b/src/main/java/com/ffii/tls/common/service/MaterialService.java
new file mode 100644
index 0000000..735cec6
--- /dev/null
+++ b/src/main/java/com/ffii/tls/common/service/MaterialService.java
@@ -0,0 +1,265 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.common.service;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.ExcelUtils;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.SecurityUtils;
+import com.ffii.core.web.AbstractService;
+import com.ffii.tbms.common.MaterialBand;
+import com.ffii.tbms.common.MaterialItem;
+import com.ffii.tbms.common.dao.MaterialBandDao;
+import com.ffii.tbms.common.dao.MaterialItemDao;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+
+@Service
+public class MaterialService extends AbstractService {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ @Autowired
+ private MaterialBandDao bandDao;
+
+ @Autowired
+ private MaterialItemDao itemDao;
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveOrUpdateBand(MaterialBand instance) {
+ return bandDao.saveOrUpdate(instance);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveOrUpdateBandItem(MaterialItem instance) {
+ return itemDao.saveOrUpdate(instance);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public MaterialBand findBand(Integer id) {
+ return bandDao.find(id);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public MaterialItem findItem(Integer id) {
+ return itemDao.find(id);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> searchBands() {
+ Map args = new HashMap<>();
+ args.put("sysGroupId", SecurityUtils.getUser().getSysGroupId());
+
+ StringBuilder sql = new StringBuilder(" SELECT mb.*, f.filename, f.skey "
+ +" FROM material_band mb "
+ +" LEFT JOIN files f ON f.id = mb.fileId "
+ +" WHERE mb.deleted = 0 "
+ +" AND mb.sysGroupId = :sysGroupId ");
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public Map loadBand(int id) {
+
+ Map args = new HashMap<>();
+ args.put("sysGroupId", SecurityUtils.getUser().getSysGroupId());
+ args.put("id", id);
+
+ StringBuilder sql = new StringBuilder(" SELECT "
+ +" mb.* "
+ +" FROM material_band mb "
+ +" WHERE id = :id "
+ +" AND mb.sysGroupId = :sysGroupId ");
+
+ return jdbcDao.queryForMap(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> searchBandItems(int bandId) {
+
+ Map args = new HashMap<>();
+ args.put("sysGroupId", SecurityUtils.getUser().getSysGroupId());
+ args.put("bandId", bandId);
+
+ StringBuilder sql = new StringBuilder(
+ " SELECT mi.* "
+ +" FROM material_item mi "
+ +" WHERE mi.deleted =0 "
+ +" AND mi.bandId = :bandId "
+ +" AND mi.sysGroupId = :sysGroupId "
+ +" ORDER BY mi.orderIdx, mi.id ");
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public Map loadBandItem(int id) {
+
+ Map args = new HashMap<>();
+ args.put("sysGroupId", SecurityUtils.getUser().getSysGroupId());
+ args.put("id", id);
+
+ StringBuilder sql = new StringBuilder(" SELECT "
+ +" mi.* "
+ +" FROM material_item mi "
+ +" WHERE mi.id = :id"
+ +" AND mi.sysGroupId = :sysGroupId " );
+
+ return jdbcDao.queryForMap(sql.toString(), args);
+ }
+
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> searchBandIcon(int bandId) {
+
+ Map args = new HashMap<>();
+ args.put("sysGroupId", SecurityUtils.getUser().getSysGroupId());
+ args.put("bandId", bandId);
+
+ StringBuilder sql = new StringBuilder(
+ " SELECT f.filename, f.id, f.skey, f.imageHeight, f.imageWidth "
+ +" FROM files_ref fr "
+ +" LEFT JOIN files f ON fr.fileId = f.id "
+ +" WHERE fr.refType = 'bandIcon' AND fr.refId = :bandId "
+ +" AND fr.sysGroupId = :sysGroupId ");
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public boolean updateItemOrderIdx(int id, int orderIdx) {
+ return jdbcDao.executeUpdate("UPDATE material_item SET orderIdx = :orderIdx WHERE id = :id",
+ MapUtils.toHashMap("id", id, "orderIdx", orderIdx)) > 0;
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public boolean importDataFromExcel(Workbook wb) {
+ Sheet sheet = wb.getSheetAt(0);
+ int bankRow = 0;
+ int formulaRow = 1;
+ int itemnameRow = 2;
+ int itemname2Row = 3;
+ int bunchRow = 4;
+ int costRow = 5;
+ int suitRow = 6;
+ int jacketRow = 7;
+ int overcoatRow = 8;
+ int pantsRow = 9;
+ int otherRow = 10;
+
+ int rownum = 1;
+ boolean next = true;
+
+ int bandId = 0;
+ while(next){
+ Row row = sheet.getRow(rownum);
+ if (row == null){
+ next = false;
+ break;
+ }
+
+ String bankName=null;
+ if(row.getCell(bankRow)!=null){
+ bankName = ExcelUtils.getStringValue(row.getCell(bankRow));
+ }
+
+ String formula=null;
+ if(row.getCell(formulaRow)!=null){
+ formula = ExcelUtils.getStringValue(row.getCell(formulaRow));
+ }
+
+ if(bankName != null){
+ MaterialBand band = new MaterialBand();
+ band.setName(bankName);
+ band.setExpectedPriceFormula(formula);
+ bandId = saveOrUpdateBand(band);
+ }
+
+ String itemName=null;
+ if(row.getCell(itemnameRow)!=null){
+ itemName = ExcelUtils.getStringValue(row.getCell(itemnameRow));
+ }
+
+ if(row.getCell(itemname2Row)!=null){
+ String name2 = ExcelUtils.getStringValue(row.getCell(itemname2Row));
+ if(name2.length()>0)
+ itemName = itemName==null?name2:itemName+" ("+name2+")";
+ }
+
+ String bunch = null;
+ if(row.getCell(bunchRow)!=null){
+ bunch = ExcelUtils.getStringValue(row.getCell(bunchRow));
+ }
+
+ BigDecimal cost = null;
+ if(row.getCell(costRow)!=null){
+ cost = ExcelUtils.getDecimalValue(row.getCell(costRow),null);
+ }
+
+ BigDecimal suit = null;
+ if(row.getCell(suitRow)!=null){
+ suit = ExcelUtils.getDecimalValue(row.getCell(suitRow),null);
+ }
+
+ BigDecimal jacket = null;
+ if(row.getCell(jacketRow)!=null){
+ jacket = ExcelUtils.getDecimalValue(row.getCell(jacketRow),null);
+ }
+
+ BigDecimal overcoat = null;
+ if(row.getCell(overcoatRow)!=null){
+ overcoat = ExcelUtils.getDecimalValue(row.getCell(overcoatRow),null);
+ }
+
+ BigDecimal pants = null;
+ if(row.getCell(pantsRow)!=null){
+ pants = ExcelUtils.getDecimalValue(row.getCell(pantsRow),null);
+ }
+
+ BigDecimal other = null;
+ if(row.getCell(otherRow)!=null){
+ other = ExcelUtils.getDecimalValue(row.getCell(otherRow),null);
+ }
+
+ MaterialItem item = new MaterialItem();
+ item.setBandId(bandId);
+ item.setName(itemName);
+ item.setBunch(bunch);
+ item.setCost(cost);
+ item.setSuitPrice(suit);
+ item.setJacketPrice(jacket);
+ item.setOvercoatPrice(overcoat);
+ item.setPantsPrice(pants);
+ item.setOtherPrice(other);
+
+ saveOrUpdateBandItem(item);
+
+ rownum++;
+
+ }
+ return true;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/common/web/MaterialController.java b/src/main/java/com/ffii/tls/common/web/MaterialController.java
new file mode 100644
index 0000000..9e69f4b
--- /dev/null
+++ b/src/main/java/com/ffii/tls/common/web/MaterialController.java
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * Copyright 2014 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the PCS project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.common.web;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.poi.poifs.filesystem.OfficeXmlFileException;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.util.Assert;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.SecurityUtils;
+import com.ffii.core.web.AbstractController;
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.common.MaterialBand;
+import com.ffii.tbms.common.MaterialItem;
+import com.ffii.tbms.common.service.MaterialService;
+import com.ffii.tbms.file.FileRefType;
+import com.ffii.tbms.file.service.FileService;
+import com.ffii.core.utils.web.ServletRequestUtils;
+
+
+@Controller
+@RequestMapping(value = "/common/material")
+public class MaterialController extends AbstractController {
+
+ @Autowired
+ private MaterialService service;
+
+ @Autowired
+ private FileService fileService;
+
+ @RequestMapping(value = "/band/save", method = RequestMethod.POST)
+ public String bandSave(Model model, HttpServletRequest request,
+ @RequestParam(defaultValue = "-1") Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+ MaterialBand instance;
+ if (id > 0) {
+ instance = service.findBand(id);
+ } else {
+ instance = new MaterialBand();
+ }
+ ServletRequestUtils.doBind(request, instance, null);
+ id = service.saveOrUpdateBand(instance);
+
+ model.addAttribute(Params.ID, id);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "/band/icon", method = { RequestMethod.GET, RequestMethod.POST })
+ public String getIcon(Model model, HttpServletRequest request, @RequestParam Integer bandId) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_VIEW")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+ List> list = service.searchBandIcon(bandId);
+ model.addAttribute(Params.RECORDS, list);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/band/delete", method = RequestMethod.POST)
+ public String bandDelete(Model model, HttpServletRequest request, @RequestParam Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+
+ // find, mark delete, and save
+ MaterialBand instance = service.findBand(id);
+ instance.setDeleted(true);
+ service.saveOrUpdateBand(instance);
+
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/band/icon/delete", method = RequestMethod.POST)
+ public String bandIconDelete(Model model, HttpServletRequest request, @RequestParam Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+
+ // find, mark delete, and save
+ MaterialBand instance = service.findBand(id);
+
+ // search for existing file(s) with the same refType, refId, and filename
+ List> existingFiles = fileService.searchFiles(
+ MapUtils.toHashMap("refType", FileRefType.BAND_ICON, "refId", id));
+
+ // delete them if found
+ for (Map existingFile : existingFiles) {
+ fileService.deleteFile((Integer) existingFile.get("id"), (Integer) existingFile.get("refId"),
+ (String) existingFile.get("refType"), (String) existingFile.get("skey"));
+ }
+
+ instance.setFileId(null);
+ service.saveOrUpdateBand(instance);
+
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "/band/list.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String bandList(Model model, HttpServletRequest request) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_VIEW")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+
+ List> list = service.searchBands();
+ model.addAttribute(Params.RECORDS, list);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/band/load", method = { RequestMethod.GET, RequestMethod.POST })
+ public String loadBand(Model model, HttpServletRequest request, @RequestParam Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_VIEW")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+
+ Map data = service.loadBand(id);
+
+ model.addAttribute(Params.DATA, data);
+ model.addAttribute(Params.SUCCESS, data != null);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "/item/save", method = RequestMethod.POST)
+ public String itemSave(Model model, HttpServletRequest request, @RequestParam(defaultValue = "-1") Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+ MaterialItem instance;
+ if (id > 0) {
+ instance = service.findItem(id);
+ } else {
+ instance = new MaterialItem();
+ }
+ ServletRequestUtils.doBind(request, instance, null);
+ id = service.saveOrUpdateBandItem(instance);
+
+
+ model.addAttribute(Params.ID, id);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/item/delete", method = RequestMethod.POST)
+ public String itemDelete(Model model, HttpServletRequest request, @RequestParam Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+
+ // find, mark delete, and save
+ MaterialItem instance = service.findItem(id);
+ instance.setDeleted(true);
+ service.saveOrUpdateBandItem(instance);
+
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/item/list.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String itemList(Model model, HttpServletRequest request, @RequestParam Integer bandId) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_VIEW")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+ List> list = service.searchBandItems(bandId);
+ model.addAttribute(Params.RECORDS, list);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "/item/load", method = { RequestMethod.GET, RequestMethod.POST })
+ public String loadItem(Model model, HttpServletRequest request, @RequestParam Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_VIEW")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+ Map data = service.loadBandItem(id);
+
+ model.addAttribute(Params.DATA, data);
+ model.addAttribute(Params.SUCCESS, data != null);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "/item/import", method = { RequestMethod.GET, RequestMethod.POST })
+ public String ImportItem(Model model, HttpServletRequest request) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_VIEW")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+
+ MultipartFile multipartFile = ((MultipartHttpServletRequest) request).getFile("multipartFile");
+ Assert.notNull(multipartFile, "multipartFile cannot be null!");
+ Workbook wb = null;
+ try {
+ wb = new XSSFWorkbook(multipartFile.getInputStream());
+ } catch (final OfficeXmlFileException oe) {
+ logger.warn("wb is xlsx, ignored");
+ }
+ Assert.notNull(wb, "Workbook cannot be null!");
+
+
+ model.addAttribute(Params.SUCCESS, service.importDataFromExcel(wb));
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+ @RequestMapping(value = "/item/set-orderIdx", method = { RequestMethod.GET, RequestMethod.POST })
+ public String setOrderIdx(Model model, HttpServletRequest request) throws Exception {
+
+ if (SecurityUtils.isGranted("SUPERUSER")
+ || SecurityUtils.isGranted("PRICE_LIST_VIEW")
+ || SecurityUtils.isGranted("PRICE_LIST_MAINTAIN")) {
+
+ List> id_orderIdx = (List>) ServletRequestUtils.getJsonListParameter(request, "id_orderIdx");
+
+ for(Map item: id_orderIdx){
+ service.updateItemOrderIdx(item.get("id"), item.get("orderIdx"));
+ }
+
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/counter/IdCounter.java b/src/main/java/com/ffii/tls/counter/IdCounter.java
new file mode 100644
index 0000000..bbd3f03
--- /dev/null
+++ b/src/main/java/com/ffii/tls/counter/IdCounter.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.counter;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import com.ffii.core.BaseEntity;
+
+/**
+ * @author Patrick
+ */
+@Entity
+@Table(name = "id_counter")
+public class IdCounter extends BaseEntity {
+
+ private static final long serialVersionUID = 2968431879107638750L;
+
+ @Column(columnDefinition = "varchar(50)")
+ private String name;
+
+ @Column(nullable = false)
+ private int count;
+
+ // min. length for code formatting
+ @Column(nullable = false)
+ private int length;
+
+ /** Default constructor */
+ public IdCounter() {
+
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public void setLength(int length) {
+ this.length = length;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/counter/dao/IdCounterDao.java b/src/main/java/com/ffii/tls/counter/dao/IdCounterDao.java
new file mode 100644
index 0000000..7f3b6bb
--- /dev/null
+++ b/src/main/java/com/ffii/tls/counter/dao/IdCounterDao.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.counter.dao;
+
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import com.ffii.core.dao.HibernateDao;
+import com.ffii.tbms.counter.IdCounter;
+
+@Repository
+public class IdCounterDao extends HibernateDao {
+
+ @Autowired
+ public IdCounterDao(SessionFactory sessionFactory) {
+ super(IdCounter.class);
+ setSessionFactory(sessionFactory);
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/counter/service/IdCounterService.java b/src/main/java/com/ffii/tls/counter/service/IdCounterService.java
new file mode 100644
index 0000000..8ae2cc6
--- /dev/null
+++ b/src/main/java/com/ffii/tls/counter/service/IdCounterService.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.counter.service;
+
+import java.text.DecimalFormat;
+import java.util.Map;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.web.AbstractService;
+import com.ffii.tbms.counter.IdCounter;
+import com.ffii.tbms.counter.dao.IdCounterDao;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author Patrick
+ */
+@Service
+public class IdCounterService extends AbstractService {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ @Autowired
+ private IdCounterDao idCounterDao;
+
+ // DecimalFormat cache
+ private static final DecimalFormat FORMATTER[] = { new DecimalFormat(""),
+ new DecimalFormat("0"), new DecimalFormat("00"), new DecimalFormat("000"), new DecimalFormat("0000"), new DecimalFormat("00000"),
+ new DecimalFormat("000000"), new DecimalFormat("0000000"), new DecimalFormat("00000000"), new DecimalFormat("000000000"),
+ new DecimalFormat("0000000000"), new DecimalFormat("00000000000"), new DecimalFormat("000000000000") };
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveOrUpdate(IdCounter instance) {
+ return idCounterDao.saveOrUpdate(instance);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public IdCounter find(Integer id) {
+ return idCounterDao.find(id);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public IdCounter find(String name) {
+ return idCounterDao.findByQuery("from com.ffii.tbms.counter.IdCounter idc where idc.name = ?", name);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public Map queryForMap(String name) {
+ String sql = "SELECT * FROM id_counter WHERE name = :name";
+
+ return jdbcDao.queryForMap(sql, MapUtils.toHashMap(Params.NAME, name));
+ }
+
+ /**
+ * Get the next IdCounter (will be created if not found) value as formatted String with the minimum length specified.
+ *
+ * The counter value will be incremented and saved.
+ *
+ * @param name
+ * the name of the IdCounter
+ * @param length
+ * the minimum length of the string to return (e.g. a length of 3 will return 012
if the counter value is 12), however it doesn't
+ * limit the maximum size of the counter value (e.g. a length of 3 will return 1234
if the counter value is 1234)
+ *
+ * @return the next IdCounter value as formatted String with the minimum length specified
+ */
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public String getNextCountString(String name, int length) {
+ if (length < 0 || length > 12) throw new IllegalArgumentException("The length must be between 0 to 12");
+ return FORMATTER[length].format(getNextCount(name, length));
+ }
+
+ /**
+ * Get the next IdCounter (will be created if not found) preview value as formatted String with the minimum length specified.
+ *
+ * @param name
+ * the name of the IdCounter
+ * @param length
+ * the minimum length of the string to return (e.g. a length of 3 will return 012
if the counter value is 12), however it doesn't
+ * limit the maximum size of the counter value (e.g. a length of 3 will return 1234
if the counter value is 1234)
+ *
+ * @return the next IdCounter preview value as formatted String with the minimum length specified
+ */
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public String getNextCountPreviewString(String name, int length) {
+ if (length < 0 || length > 12) throw new IllegalArgumentException("The length must be between 0 to 12");
+ return FORMATTER[length].format(getNextCountPreview(name, length));
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public int getNextCount(String name, int length) {
+
+ // find existing IdCounter
+ IdCounter instance = find(name);
+
+ if (instance == null) {
+ // create IdCounter if not found
+ instance = new IdCounter();
+ instance.setName(name);
+ instance.setLength(length);
+ instance.setCount(1);
+ saveOrUpdate(instance);
+ } else {
+ // else just increment and update IdCounter
+ instance.setCount(instance.getCount() + 1);
+ saveOrUpdate(instance);
+ }
+
+ // return the incremented counter value
+ return instance.getCount();
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public int getNextCountPreview(String name, int length) {
+
+ // find existing IdCounter
+ IdCounter instance = find(name);
+
+ if (instance == null) {
+ // create IdCounter if not found
+ instance = new IdCounter();
+ instance.setName(name);
+ instance.setLength(length);
+ saveOrUpdate(instance);
+ }
+
+ // return the incremented counter value
+ return instance.getCount() + 1;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/customer/Customer.java b/src/main/java/com/ffii/tls/customer/Customer.java
new file mode 100644
index 0000000..4c94828
--- /dev/null
+++ b/src/main/java/com/ffii/tls/customer/Customer.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * Copyright 2013 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the PCS project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.customer;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import com.ffii.core.BaseEntity;
+
+@Entity
+@Table(name = "customer")
+public class Customer extends BaseEntity {
+
+ private static final long serialVersionUID = -7086241311009708530L;
+
+
+ @Column(columnDefinition = "varchar(255)")
+ private String surname;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String firstName;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String nameCh;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String phone1;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String phone2;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String companyName;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String companyPhone;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String companyEmail;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String companyAddress;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String email;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String address;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String remarks;
+
+ @Column(columnDefinition = "int(11)")
+ private Integer ref;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String tag;
+
+
+ public Customer() {
+ }
+
+ public String getSurname() {
+ return surname;
+ }
+
+ public void setSurname(String surname) {
+ this.surname = surname;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getNameCh() {
+ return nameCh;
+ }
+
+ public void setNameCh(String nameCh) {
+ this.nameCh = nameCh;
+ }
+
+ public String getPhone1() {
+ return phone1;
+ }
+
+ public void setPhone1(String phone1) {
+ this.phone1 = phone1;
+ }
+
+ public String getPhone2() {
+ return phone2;
+ }
+
+ public void setPhone2(String phone2) {
+ this.phone2 = phone2;
+ }
+
+ public String getCompanyPhone() {
+ return companyPhone;
+ }
+
+ public void setCompanyPhone(String companyPhone) {
+ this.companyPhone = companyPhone;
+ }
+
+ public String getCompanyEmail() {
+ return companyEmail;
+ }
+
+ public void setCompanyEmail(String companyEmail) {
+ this.companyEmail = companyEmail;
+ }
+
+ public String getCompanyAddress() {
+ return companyAddress;
+ }
+
+ public void setCompanyAddress(String companyAddress) {
+ this.companyAddress = companyAddress;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getRemarks() {
+ return remarks;
+ }
+
+ public void setRemarks(String remarks) {
+ this.remarks = remarks;
+ }
+
+ public Integer getRef() {
+ return ref;
+ }
+
+ public void setRef(Integer ref) {
+ this.ref = ref;
+ }
+
+ public String getCompanyName() {
+ return companyName;
+ }
+
+ public void setCompanyName(String companyName) {
+ this.companyName = companyName;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public void setTag(String tag) {
+ this.tag = tag;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/customer/dao/CustomerDao.java b/src/main/java/com/ffii/tls/customer/dao/CustomerDao.java
new file mode 100644
index 0000000..1b7aaa4
--- /dev/null
+++ b/src/main/java/com/ffii/tls/customer/dao/CustomerDao.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright 2013 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the PCS project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.customer.dao;
+
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import com.ffii.core.dao.HibernateDao;
+import com.ffii.tbms.customer.Customer;
+
+@Repository
+public class CustomerDao extends HibernateDao {
+
+ @Autowired
+ public CustomerDao(SessionFactory sessionFactory) {
+ super(Customer.class);
+ setSessionFactory(sessionFactory);
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/customer/service/CustomerService.java b/src/main/java/com/ffii/tls/customer/service/CustomerService.java
new file mode 100644
index 0000000..539f65f
--- /dev/null
+++ b/src/main/java/com/ffii/tls/customer/service/CustomerService.java
@@ -0,0 +1,369 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.customer.service;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.BooleanUtils;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.SecurityUtils;
+import com.ffii.core.web.AbstractService;
+import com.ffii.tbms.customer.Customer;
+import com.ffii.tbms.customer.dao.CustomerDao;
+import com.ffii.tbms.order.Order;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * @author Patrick
+ */
+@Service
+public class CustomerService extends AbstractService {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ @Autowired
+ private CustomerDao customerDao;
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveOrUpdate(Customer instance) {
+ return customerDao.saveOrUpdate(instance);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public Customer find(Integer id) {
+ return customerDao.find(id);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> searchForCombo(Map args) {
+
+ SecurityUtils.authArgs(args);
+
+ StringBuilder sql = new StringBuilder(" SELECT "
+ +" c.id, "
+ +" c.phone1, "
+ +" c.phone2, "
+ +" concat_ws(' / ', c.phone1, c.phone2) AS phone, "
+ +" CONCAT(IF(c.firstName IS NULL, '', CONCAT(c.firstName, ' ')), "
+ +" IF(c.surname IS NULL, '', CONCAT(c.surname, ' ')), "
+ +" IF(c.nameCh IS NULL, '', c.nameCh)) AS customerName "
+ +" FROM customer c WHERE deleted = 0 ");
+
+ if (args.containsKey(Params.QUERY))
+ sql.append(" AND ( concat_ws(' ', c.firstName ,c.surname, c.nameCh, c.phone1, c.phone2) LIKE :query "
+ +" OR concat_ws(' ', c.surname, c.firstName, c.nameCh, phone1, phone2) LIKE :query ) ");
+
+ if (args.containsKey(Params.ID))
+ sql.append(" AND c.id = :id");
+
+ if(args.containsKey("sysGroupId")){
+ sql.append(" AND c.sysGroupId = :sysGroupId ");
+ }
+
+ sql.append(" ORDER BY c.surname, c.firstName ");
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ private String getSearchCriteriaString(Map args){
+ StringBuilder sql = new StringBuilder("");
+
+ if (args != null) {
+ if (args.containsKey(Params.ID))
+ sql.append(" AND c.id = :id");
+ if (args.containsKey("surname"))
+ sql.append(" AND c.surname LIKE :surname ");
+ if (args.containsKey("firstName"))
+ sql.append(" AND c.firstName LIKE :firstName ");
+ if (args.containsKey("nameCh"))
+ sql.append(" AND c.nameCh LIKE :nameCh ");
+ if(args.containsKey("sysGroupId")){
+ sql.append(" AND c.sysGroupId = :sysGroupId ");
+ }
+ if (args.containsKey("contact"))
+ sql.append(" AND (c.phone1 LIKE :contact OR c.phone2 LIKE :contact "
+ + " OR c.address LIKE :contact OR c.companyPhone LIKE :contact "
+ + " OR c.companyAddress LIKE :contact ) ");
+ if (args.containsKey("email"))
+ sql.append(" AND (c.email LIKE :email OR c.companyEmail LIKE :email ) ");
+
+ if (args.containsKey("tag")){
+ String str = args.get("tag").toString().trim();
+ String[] array = str.split(" ");
+ str = "";
+ for(String tag: array){
+ if(!str.equals("")){
+ str = str+" OR ";
+ }
+ str = str+" c.tag LIKE '%"+tag+"%' ";
+ }
+ sql.append(" AND ( "+str+" ) ");
+ }
+
+ //search customer order
+ if((args.containsKey("onHand") && BooleanUtils.isTrue(args.get("onHand")))
+ || args.containsKey("orderType")
+ || args.containsKey("orderCode")
+ || args.containsKey("fromDate")
+ || args.containsKey("toDate")){
+
+ sql.append(" AND EXISTS( SELECT 1 FROM orders o "
+ +" WHERE o.custId = c.id "
+ +" AND o.deleted = 0 "
+ +" AND o.status != "+Order.STATUS_CANCELLED+" ");
+
+ if (args.containsKey("onHand") && BooleanUtils.isTrue(args.get("onHand")))
+ sql.append(" AND o.status = "+Order.STATUS_ON_HAND+" ");
+ if (args.containsKey("orderType"))
+ sql.append(" AND o.type = :orderType ");
+ if (args.containsKey("orderCode"))
+ sql.append(" AND o.code LIKE :orderCode ");
+ if (args.containsKey("fromDate"))
+ sql.append(" AND o.targetCompleteDate >= :fromDate ");
+ if (args.containsKey("toDate"))
+ sql.append(" AND o.targetCompleteDate < :toDate ");
+
+ sql.append(" ) ");
+ }
+
+ //search isBalance
+ if (args.containsKey("balance") && BooleanUtils.isTrue(args.get("balance")))
+ sql.append(" AND IFNULL(custOrders.amount,0) > IFNULL(custPayment.amount,0) ");
+
+ }
+
+ return sql.toString();
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> search(Map args) {
+
+ SecurityUtils.authArgs(args);
+
+ StringBuilder sql = new StringBuilder(
+ " SELECT "
+ +" c.id,"
+ +" c.surname, "
+ +" c.firstName, "
+ +" c.nameCh, "
+ +" CONCAT(IF(c.firstName IS NULL, '', CONCAT(c.firstName, ' ')), "
+ +" IF(c.surname IS NULL, '', CONCAT(c.surname, ' ')), "
+ +" IF(c.nameCh IS NULL, '', c.nameCh)) AS customerName, "
+ +" c.phone1, "
+ +" c.phone2, "
+ +" c.companyPhone, "
+ +" c.companyEmail, "
+ +" c.companyAddress, "
+ +" c.email, "
+ +" c.address, "
+ +" c.remarks, "
+ +" c.tag, "
+ +" lastMeetingDate.date As lastMeetingDate, "
+ +" nextMeetingDate.date AS nextMeetingDate, "
+ +" refCust.customerName AS refCustomerName, "
+ +" IFNULL(custOrders.amount,0) AS orderAmount, "
+ +" IFNULL(custPayment.amount,0) AS paymentAmount, "
+ +" (IFNULL(custOrders.amount,0) <= IFNULL(custPayment.amount,0)) AS isBalance, "
+ +" minTargetDate.targetCompleteDate AS targetCompleteDate,"
+ +" custOrders.onHandOrderNum "
+ +" FROM "
+ +" customer c "
+ +" LEFT JOIN "
+ +" (SELECT "
+ +" CONCAT(IF(_c.firstName IS NULL, '', CONCAT(_c.firstName, ' ')), "
+ +" IF(_c.surname IS NULL, '', CONCAT(_c.surname, ' ')), "
+ +" IF(_c.nameCh IS NULL, '', _c.nameCh)) AS customerName, "
+ +" _c.id "
+ +" FROM customer _c ) refCust ON refCust.id = c.ref "
+
+ +" LEFT JOIN "
+ +" (SELECT "
+ +" o.custId, SUM(oi.price) AS amount, "
+ +" SUM(IF(o.status = "+Order.STATUS_ON_HAND+", 1, 0)) AS onHandOrderNum "
+ +" FROM"
+ +" orders o "
+ +" LEFT JOIN order_item oi ON oi.orderId = o.id "
+ +" WHERE "
+ +" o.deleted = 0 AND oi.deleted = 0"
+ +" AND o.status != - 1"
+ +" GROUP BY o.custId) custOrders ON custOrders.custId = c.id"
+
+ +" LEFT JOIN"
+ +" (SELECT "
+ +" o.custId, SUM(p.amount) AS amount"
+ +" FROM"
+ +" orders o"
+ +" LEFT JOIN payment p ON p.orderId = o.id"
+ +" WHERE"
+ +" o.deleted = 0 AND p.deleted = 0 "
+ //+" AND o.ignoreC = 0 "
+ +" AND o.status != - 1 "
+ +" GROUP BY o.custId) custPayment ON custPayment.custId = c.id"
+
+ +" LEFT JOIN "
+ +" ( SELECT Max(m.date) AS date, m.custId "
+ +" FROM meeting m "
+ +" WHERE m.deleted = 0 "
+ +" AND DATE(m.date) < DATE(current_date()) "
+ +" GROUP BY m.custId ) lastMeetingDate ON lastMeetingDate.custId = c.id "
+
+ +" LEFT JOIN "
+ +" ( SELECT MIN(m.date) AS date, m.custId "
+ +" FROM meeting m "
+ +" WHERE m.deleted = 0 "
+ +" AND DATE(m.date) >= DATE(current_date()) "
+ +" GROUP BY m.custId ) nextMeetingDate ON nextMeetingDate.custId = c.id "
+
+ +"LEFT JOIN "
+ +" ( SELECT o.custId, "
+ +" MIN(o.targetCompleteDate) AS targetCompleteDate "
+ +" FROM orders o "
+ +" WHERE o.deleted = 0 "
+ +" AND o.status = "+Order.STATUS_ON_HAND+" "
+ +" AND o.targetCompleteDate IS NOT NULL "
+ +" GROUP BY o.custId) minTargetDate ON minTargetDate.custId = c.id "
+
+ +" WHERE"
+ +" c.deleted = 0 ");
+
+ sql.append(getSearchCriteriaString(args));
+
+ sql.append(" ORDER BY ( 0+0 ");
+
+ if (args != null) {
+ if (args.containsKey("_surname"))
+ sql.append(" + IF( c.surname = :_surname,1,0) + IF( c.surname LIKE '"+args.get("_surname")+"%',1,0) ");
+ if (args.containsKey("_firstName"))
+ sql.append(" + IF( c.firstName = :_firstName,1,0) + IF( c.firstName LIKE '"+args.get("_firstName")+"%',1,0) ");
+ if (args.containsKey("_nameCh"))
+ sql.append(" + IF( c.nameCh = :_nameCh,1,0) + IF( c.nameCh LIKE '"+args.get("_nameCh")+"%',1,0) ");
+ }
+ sql.append(" ) DESC, ");
+ sql.append(" TRIM(c.firstName), TRIM(c.surname), TRIM(c.nameCh), c.id ");
+
+ if (args != null) {
+ if (args.containsKey("start") && args.containsKey("limit"))
+ sql.append(" LIMIT :start, :limit ");
+ }
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public Map countSearch(Map args) {
+
+ SecurityUtils.authArgs(args);
+
+ StringBuilder sql = new StringBuilder(
+ " SELECT "
+ +" COUNT(*) AS count "
+ +" FROM"
+ +" ( SELECT "
+ +" c.id "
+ +" FROM "
+ +" customer c "
+ +" LEFT JOIN "
+
+ +" (SELECT "
+ +" o.custId, SUM(oi.price) AS amount, "
+ +" SUM(IF(o.status = "+Order.STATUS_ON_HAND+", 1, 0)) AS onHandOrderNum "
+ +" FROM"
+ +" orders o "
+ +" LEFT JOIN order_item oi ON oi.orderId = o.id "
+ +" WHERE "
+ +" o.deleted = 0 AND oi.deleted = 0"
+ +" AND o.status != - 1"
+ +" GROUP BY o.custId) custOrders ON custOrders.custId = c.id"
+
+ +" LEFT JOIN"
+ +" (SELECT "
+ +" o.custId, SUM(p.amount) AS amount"
+ +" FROM"
+ +" orders o"
+ +" LEFT JOIN payment p ON p.orderId = o.id"
+ +" WHERE"
+ +" o.deleted = 0 AND p.deleted = 0 "
+ //+" AND o.ignoreC = 0 "
+ +" AND o.status != - 1"
+ +" GROUP BY o.custId) custPayment ON custPayment.custId = c.id"
+
+ +" WHERE"
+ +" c.deleted = 0");
+
+ sql.append(getSearchCriteriaString(args));
+
+ sql.append(") _t");
+
+ return jdbcDao.queryForMap(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public Map queryForMap(Integer id) {
+ Map args = MapUtils.toHashMap(Params.ID, id);
+ SecurityUtils.authArgs(args);
+
+ String sql = "SELECT c.*, "
+ +" CONCAT_WS(' ',c.firstName , c.surname , c.nameCh) AS customerName, "
+ +" CONCAT_WS('/',c.phone1, c.phone2) AS phoneNum "
+ +" FROM customer c WHERE c.id = :id ";
+
+ if(args.containsKey("sysGroupId")){
+ sql+=" AND c.sysGroupId = :sysGroupId ";
+ }
+ return jdbcDao.queryForMap(sql, args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public Map getOrderIds(Map args) {
+
+ SecurityUtils.authArgs(args);
+
+ String sql = "SELECT GROUP_CONCAT(o.id ORDER BY o.date DESC, o.id DESC) AS ids "
+ +" FROM orders o "
+ +" WHERE 1=1 "
+ +" AND o.custId = :custId ";
+
+ if (args != null) {
+ if (args.containsKey("ignoreOrderId"))
+ sql+=" AND o.id != :ignoreOrderId ";
+
+ if(args.containsKey("sysGroupId")){
+ sql+=" AND o.sysGroupId = :sysGroupId ";
+ }
+ }
+ sql+=" GROUP BY o.custId ";
+
+ return jdbcDao.queryForMap(sql, args);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> getRelatedOrder(int id) {
+
+ Map args = MapUtils.toHashMap(Params.ID, id);
+ SecurityUtils.authArgs(args);
+
+ String sql = " SELECT o.* FROM orders o WHERE o.custId = :id ";
+ if (args != null) {
+
+ if(args.containsKey("sysGroupId")){
+ sql+=" AND o.sysGroupId = :sysGroupId ";
+ }
+ }
+ return jdbcDao.queryForList(sql, args);
+ }
+}
diff --git a/src/main/java/com/ffii/tls/customer/web/CustomerController.java b/src/main/java/com/ffii/tls/customer/web/CustomerController.java
new file mode 100644
index 0000000..b468ffa
--- /dev/null
+++ b/src/main/java/com/ffii/tls/customer/web/CustomerController.java
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * Copyright 2014 2Fi Business Solutions Ltd.
+ *
+ * This code is part of the PCS project.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.customer.web;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.crypto.password.StandardPasswordEncoder;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import com.ffii.core.utils.CriteriaUtils;
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.SecurityUtils;
+import com.ffii.core.utils.web.ServletRequestUtils;
+import com.ffii.core.web.AbstractController;
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.customer.Customer;
+import com.ffii.tbms.customer.service.CustomerService;
+import com.ffii.tbms.file.FileRefType;
+import com.ffii.tbms.file.service.FileService;
+import com.ffii.tbms.log.LogUtils;
+import com.ffii.tbms.order.Order;
+import com.ffii.tbms.order.service.OrderService;
+
+@Controller
+@RequestMapping(value = "/customer")
+public class CustomerController extends AbstractController {
+
+ @Autowired
+ private CustomerService customerService;
+
+ @Autowired
+ private FileService fileService;
+
+ @Autowired
+ private OrderService orderService;
+
+ @Autowired
+ private StandardPasswordEncoder passwordEncoder;
+
+ @RequestMapping(value = "/save", method = RequestMethod.POST)
+ public String save(Model model, HttpServletRequest request, @RequestParam(defaultValue = "-1") Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("CUSTOMER_MAINTAIN")) {
+ Customer instance;
+ if (id > 0) {
+ instance = customerService.find(id);
+ } else {
+ instance = new Customer();
+ }
+
+ ServletRequestUtils.doBind(request, instance, null);
+ id = customerService.saveOrUpdate(instance);
+
+ model.addAttribute(Params.ID, id);
+
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/delete", method = RequestMethod.POST)
+ public String delete(Model model, HttpServletRequest request, @RequestParam Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("CUSTOMER_MAINTAIN") && SecurityUtils.isGranted("CUSTOMER_DELETE")) {
+
+ // find, mark delete, and save
+ Customer instance = customerService.find(id);
+ instance.setDeleted(true);
+ customerService.saveOrUpdate(instance);
+
+ List> orderList = customerService.getRelatedOrder(id);
+ for(Map o : orderList){
+ orderService.deletedOrder(NumberUtils.intValue(o.get("id"), 0));
+ }
+
+
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ model.addAttribute(Params.AUTH, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/load", method = { RequestMethod.GET, RequestMethod.POST })
+ public String load(Model model, HttpServletRequest request, @RequestParam Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("CUSTOMER_MAINTAIN")) {
+ Map data = customerService.queryForMap(id);
+
+ model.addAttribute(Params.DATA, data);
+ model.addAttribute(Params.SUCCESS, data != null);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/combo.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String comboJson(Model model, HttpServletRequest request) throws Exception {
+
+ Map args = new HashMap();
+
+ CriteriaUtils.addInteger(request, args, Params.ID);
+ CriteriaUtils.addStringLike(request, args, Params.QUERY);
+ CriteriaUtils.addInteger(request, args, "deptId");
+
+ List> records = customerService.searchForCombo(args);
+
+ model.addAttribute(Params.RECORDS, records);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/list.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String listJson(Model model, HttpServletRequest request) throws Exception {
+
+ if (SecurityUtils.isGranted("CUSTOMER_MAINTAIN")) {
+ Map args = new HashMap();
+
+ CriteriaUtils.addStringLike(request, args, Params.QUERY);
+
+ CriteriaUtils.addInteger(request, args, Params.ID);
+ CriteriaUtils.addString(request, args, "surname");
+ CriteriaUtils.addString(request, args, "firstName");
+ CriteriaUtils.addString(request, args, "nameCh");
+ CriteriaUtils.addStringLike(request, args, "contact");
+ CriteriaUtils.addStringLike(request, args, "email");
+ CriteriaUtils.addBoolean(request, args, "onHand");
+ CriteriaUtils.addBoolean(request, args, "balance");
+ CriteriaUtils.addString(request, args, "orderType");
+ CriteriaUtils.addStringLike(request, args, "orderCode");
+ CriteriaUtils.addDate(request, args, "fromDate");
+ CriteriaUtils.addDateTo(request, args, "toDate");
+ CriteriaUtils.addString(request, args, "tag");
+
+ CriteriaUtils.addInteger(request, args, "start");
+ CriteriaUtils.addInteger(request, args, "limit");
+
+ if(args.containsKey("surname")){
+ args.put("_surname", args.get("surname"));
+ args.put("surname", "%"+args.get("surname")+"%");
+ }
+
+ if(args.containsKey("firstName")){
+ args.put("_firstName", args.get("firstName"));
+ args.put("firstName", "%"+args.get("firstName")+"%");
+ }
+
+ if(args.containsKey("nameCh")){
+ args.put("_nameCh", args.get("nameCh"));
+ args.put("nameCh", "%"+args.get("nameCh")+"%");
+ }
+
+ args.put("today", new Date());
+
+ if(args.containsKey("tag")){
+ String str = args.get("tag").toString().trim();
+ List strList = Arrays.asList(str.split(""));
+ if(strList != null && strList.size()>0){
+ for(String s: strList){
+
+ }
+ }else{
+ args.remove("tag");
+ }
+ }
+
+ List> records = customerService.search(args);
+ Map count = customerService.countSearch(args);
+
+ model.addAttribute(Params.RECORDS, records);
+ model.addAttribute("total", NumberUtils.intValue(count.get("count")));
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/image-list.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String imageListJson(Model model, HttpServletRequest request, @RequestParam Integer id) throws Exception {
+
+ if (SecurityUtils.isGranted("CUSTOMER_MAINTAIN")) {
+ Map args = new HashMap();
+ args.put("refType", FileRefType.CUSTOMER);
+ args.put("refId", id);
+
+ List> records = fileService.searchFiles(args);
+
+ model.addAttribute(Params.RECORDS, records);
+ model.addAttribute("title", "Customer Photo");
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/order-list", method = { RequestMethod.GET, RequestMethod.POST })
+ public String orderListJson(Model model, HttpServletRequest request, @RequestParam Integer custId) throws Exception {
+
+ if (SecurityUtils.isGranted("CUSTOMER_MAINTAIN")) {
+ Map args = new HashMap();
+ args.put("custId", custId);
+ CriteriaUtils.addInteger(request, args, "ignoreOrderId");
+
+ Map records = customerService.getOrderIds(args);
+
+ model.addAttribute(Params.RECORDS, records);
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/get-auth", method = { RequestMethod.POST })
+ public String getDeleteAuth(Model model, HttpServletRequest request, @RequestParam String v) throws Exception {
+
+ if (SecurityUtils.isGranted("CUSTOMER_MAINTAIN")) {
+
+ if(passwordEncoder.matches(v, SecurityUtils.getUser().getPassword()))
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ else
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ return JsonView.NAME;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/example/Example.java b/src/main/java/com/ffii/tls/example/Example.java
new file mode 100644
index 0000000..4a4a9e8
--- /dev/null
+++ b/src/main/java/com/ffii/tls/example/Example.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.example;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+
+import com.ffii.core.BaseEntity;
+
+@Entity
+@Table(name = "example")
+public class Example extends BaseEntity {
+
+ private static final long serialVersionUID = 6045007748715095923L;
+
+ @Column(columnDefinition = "varchar(50)", nullable = false)
+ private String name;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String description;
+
+ @Column(columnDefinition = "int", nullable = false)
+ private int intValue;
+
+ @Column(columnDefinition = "int")
+ private Integer integerValue;
+
+ @Column(columnDefinition = "decimal(12,2)")
+ private BigDecimal decimalValue;
+
+ @Column(columnDefinition = "boolean", nullable = false)
+ private boolean booleanValue;
+
+ @Column(columnDefinition = "date")
+ private Date dateValue;
+
+ @Column(columnDefinition = "datetime")
+ private Date datetimeValue;
+
+ // @Transient means it wont be saved to database
+ @Transient
+ private String transientField;
+
+ /** Default constructor */
+ public Example() {
+
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public int getIntValue() {
+ return intValue;
+ }
+
+ public void setIntValue(int intValue) {
+ this.intValue = intValue;
+ }
+
+ public Integer getIntegerValue() {
+ return integerValue;
+ }
+
+ public void setIntegerValue(Integer integerValue) {
+ this.integerValue = integerValue;
+ }
+
+ public BigDecimal getDecimalValue() {
+ return decimalValue;
+ }
+
+ public void setDecimalValue(BigDecimal decimalValue) {
+ this.decimalValue = decimalValue;
+ }
+
+ public boolean isBooleanValue() {
+ return booleanValue;
+ }
+
+ public void setBooleanValue(boolean booleanValue) {
+ this.booleanValue = booleanValue;
+ }
+
+ public Date getDateValue() {
+ return dateValue;
+ }
+
+ public void setDateValue(Date dateValue) {
+ this.dateValue = dateValue;
+ }
+
+ public Date getDatetimeValue() {
+ return datetimeValue;
+ }
+
+ public void setDatetimeValue(Date datetimeValue) {
+ this.datetimeValue = datetimeValue;
+ }
+
+ public String getTransientField() {
+ return transientField;
+ }
+
+ public void setTransientField(String transientField) {
+ this.transientField = transientField;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/example/dao/ExampleDao.java b/src/main/java/com/ffii/tls/example/dao/ExampleDao.java
new file mode 100644
index 0000000..5669379
--- /dev/null
+++ b/src/main/java/com/ffii/tls/example/dao/ExampleDao.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.example.dao;
+
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import com.ffii.core.dao.HibernateDao;
+import com.ffii.tbms.example.Example;
+
+@Repository
+public class ExampleDao extends HibernateDao {
+
+ @Autowired
+ public ExampleDao(SessionFactory sessionFactory) {
+ super(Example.class);
+ setSessionFactory(sessionFactory);
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/example/service/ExampleService.java b/src/main/java/com/ffii/tls/example/service/ExampleService.java
new file mode 100644
index 0000000..7890ade
--- /dev/null
+++ b/src/main/java/com/ffii/tls/example/service/ExampleService.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.example.service;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.DateUtils;
+import com.ffii.core.utils.ExcelUtils;
+import com.ffii.core.utils.JsonUtils;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.SecurityUtils;
+import com.ffii.core.web.AbstractService;
+import com.ffii.tbms.example.Example;
+import com.ffii.tbms.example.dao.ExampleDao;
+import com.ffii.tbms.file.service.FileService;
+import com.ffii.tbms.log.service.AuditLogService;
+
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Example Service
+ *
+ * @author Patrick
+ */
+@Service
+public class ExampleService extends AbstractService {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ @Autowired
+ private ExampleDao exampleDao;
+
+ @Autowired
+ private FileService fileService;
+
+ @Autowired
+ private ResourceLoader resourceLoader;
+
+ @Autowired
+ private AuditLogService auditLogService;
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveExample(Example instance) {
+ return exampleDao.saveOrUpdate(instance);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public int insertSomething(Map args) {
+ return jdbcDao.executeInsertAndReturnKey("example", Params.ID, args).intValue();
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public void updateStatusWithAuditLog() {
+ Map args = MapUtils.toHashMap(Params.ID, 1, "status", "pig", "mode", "B");
+ int rs = jdbcDao.executeUpdateByTable("job", args);
+
+ if (rs > 0) auditLogService.save("job", 1, SecurityUtils.getUser().getId(), new Date(), null, JsonUtils.toJsonString(args));
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public List> search(Map args) {
+
+ String sql = "SELECT 1";
+
+ return jdbcDao.queryForList(sql, args);
+ }
+
+ /**
+ * Scheduled method example using cron expression
+ * 1. return type must be void
+ * 2. must not have any parameters
+ *
+ *
+ * Cron expression:
+ * 1. Seconds: 0-59
+ * 2. Minutes: 0-59
+ * 3. Hours: 0-23
+ * 4. Day of month: 1-31
+ * 5. Month: 0-11 = Jan to Dec
+ * 6. Day of week: 1-7 = Sunday to Saturday
+ */
+ @Scheduled(cron = "0 5 0 * * ?") // everyday at 00:05:00
+ public void scheduledMethodCron() {
+ logger.info("scheduledMethodCron at 00:05:00");
+ }
+
+ @Scheduled(cron = "0 0 12 * * ?") // everyday at noon 12:00:00
+ public void scheduledExcelReports() {
+ Workbook workbook = ExcelUtils.loadXSSFWorkbookFromTemplateSource(resourceLoader, "WEB-INF/report/exampleTemplate.xlsx");
+ Sheet sheet = workbook.getSheetAt(0);
+
+ int colIndex = 1;
+ for (int i = 0; i < 10; i++) {
+ ExcelUtils.setCellValue(sheet, i, colIndex, i);
+ }
+
+ // save the Excel file
+ byte[] bytes = ExcelUtils.toByteArray(workbook);
+ fileService.saveFile("Example_Report_" + DateUtils.SQL_DATE_FORMAT.format(new Date()), "", "exampleReport", 0, "", bytes);
+ }
+
+ /**
+ * Scheduled method example using fixed rate
+ * 1. return type must be void
+ * 2. must not have any parameters
+ *
+ * fixedRate in ms, e.g. 60000ms = run every 60 seconds
+ */
+ @Scheduled(fixedRate = 60000)
+ public void scheduledMethodFixedRate() {
+ logger.debug("scheduledMethodFixedRate at 60000ms");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ffii/tls/example/web/ExampleController.java b/src/main/java/com/ffii/tls/example/web/ExampleController.java
new file mode 100644
index 0000000..39f3a1d
--- /dev/null
+++ b/src/main/java/com/ffii/tls/example/web/ExampleController.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.example.web;
+
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.example.service.ExampleService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Controller
+@RequestMapping(value = "/example")
+public class ExampleController {
+
+ @Autowired
+ private ExampleService exampleService;
+
+ @RequestMapping("/1")
+ public String example1(@RequestParam(defaultValue = "Pig") String name, Model model) {
+ model.addAttribute("name", name);
+ return JsonView.NAME;
+ }
+
+ @RequestMapping("/test2")
+ public String test2(Model model) {
+ exampleService.updateStatusWithAuditLog();
+ return JsonView.NAME;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/file/File.java b/src/main/java/com/ffii/tls/file/File.java
new file mode 100644
index 0000000..105f12e
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/File.java
@@ -0,0 +1,124 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import com.ffii.core.BaseEntity;
+
+@Entity
+@Table(name = "files")
+public class File extends BaseEntity {
+
+ private static final long serialVersionUID = -8832818363002693551L;
+
+ public static final int ONE_KILOBYTE = 1024;
+ public static final int ONE_MEGABYTE = 1048576;
+
+ @Column(columnDefinition = "varchar(20)")
+ private String systemType;
+
+ @Column(columnDefinition = "varchar(16)", nullable = false)
+ private String skey;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String description;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String filename;
+
+ @Column(columnDefinition = "varchar(255)")
+ private String mimetype;
+
+ @Column(columnDefinition = "int")
+ private long filesize;
+
+ @Column(columnDefinition = "int")
+ private int imageHeight;
+ @Column(columnDefinition = "int")
+ private int imageWidth;
+
+ /** Default constructor */
+ public File() {
+
+ }
+
+ /*
+ * getters and setters
+ */
+
+ public String getSystemType() {
+ return systemType;
+ }
+
+ public void setSystemType(String systemType) {
+ this.systemType = systemType;
+ }
+
+ public String getSkey() {
+ return skey;
+ }
+
+ public void setSkey(String skey) {
+ this.skey = skey;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ public String getMimetype() {
+ return mimetype;
+ }
+
+ public void setMimetype(String mimetype) {
+ this.mimetype = mimetype;
+ }
+
+ public long getFilesize() {
+ return filesize;
+ }
+
+ public void setFilesize(long filesize) {
+ this.filesize = filesize;
+ }
+
+ public int getImageHeight() {
+ return imageHeight;
+ }
+
+ public void setImageHeight(int imageHeight) {
+ this.imageHeight = imageHeight;
+ }
+
+ public int getImageWidth() {
+ return imageWidth;
+ }
+
+ public void setImageWidth(int imageWidth) {
+ this.imageWidth = imageWidth;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/file/FileBlob.java b/src/main/java/com/ffii/tls/file/FileBlob.java
new file mode 100644
index 0000000..15552f7
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/FileBlob.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import com.ffii.core.BaseEntity;
+
+@Entity
+@Table(name = "files_blob")
+public class FileBlob extends BaseEntity {
+
+ private static final long serialVersionUID = -9044712421536780602L;
+
+ @Column(columnDefinition = "int")
+ private int fileId;
+
+ @Column(columnDefinition = "longblob")
+ private byte[] bytes;
+
+ /** Default constructor */
+ public FileBlob() {
+
+ }
+
+ /*
+ * getters and setters
+ */
+
+ public int getFileId() {
+ return fileId;
+ }
+
+ public void setFileId(int fileId) {
+ this.fileId = fileId;
+ }
+
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ public void setBytes(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/file/FileRef.java b/src/main/java/com/ffii/tls/file/FileRef.java
new file mode 100644
index 0000000..d047a20
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/FileRef.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+import com.ffii.core.BaseEntity;
+
+@Entity
+@Table(name = "files_ref")
+public class FileRef extends BaseEntity {
+
+ private static final long serialVersionUID = -9133877150126183428L;
+
+ @Column(columnDefinition = "int", nullable = false)
+ private int fileId;
+
+ @Column(columnDefinition = "varchar(30)", nullable = false)
+ private String refType;
+
+ @Column(columnDefinition = "int", nullable = false)
+ private int refId;
+
+ @Column(columnDefinition = "varchar(50)")
+ private String refCode;
+
+ /** Default constructor */
+ public FileRef() {
+ }
+
+ /*
+ * getters and setters
+ */
+
+ public int getFileId() {
+ return fileId;
+ }
+
+ public void setFileId(int fileId) {
+ this.fileId = fileId;
+ }
+
+ public String getRefType() {
+ return refType;
+ }
+
+ public void setRefType(String refType) {
+ this.refType = refType;
+ }
+
+ public int getRefId() {
+ return refId;
+ }
+
+ public void setRefId(int refId) {
+ this.refId = refId;
+ }
+
+ public String getRefCode() {
+ return refCode;
+ }
+
+ public void setRefCode(String refCode) {
+ this.refCode = refCode;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/file/FileRefType.java b/src/main/java/com/ffii/tls/file/FileRefType.java
new file mode 100644
index 0000000..dc03a09
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/FileRefType.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file;
+
+public class FileRefType {
+
+ /** ref by Customer ID */
+ public static final String CUSTOMER = "customer";
+
+ /** ref by Order ID */
+ public static final String ORDER = "order";
+
+ /** ref by Order ID , refCode = custId*/
+ public static final String MEASUREMENT_SHEET = "ms";
+
+ /** ref by Band Id */
+ public static final String BAND_ICON = "bandIcon";
+
+
+}
diff --git a/src/main/java/com/ffii/tls/file/dao/FileBlobDao.java b/src/main/java/com/ffii/tls/file/dao/FileBlobDao.java
new file mode 100644
index 0000000..7e99a99
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/dao/FileBlobDao.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file.dao;
+
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import com.ffii.core.dao.HibernateDao;
+import com.ffii.tbms.file.FileBlob;
+
+@Repository
+public class FileBlobDao extends HibernateDao {
+
+ @Autowired
+ public FileBlobDao(SessionFactory sessionFactory) {
+ super(FileBlob.class);
+ setSessionFactory(sessionFactory);
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/file/dao/FileDao.java b/src/main/java/com/ffii/tls/file/dao/FileDao.java
new file mode 100644
index 0000000..4e910f5
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/dao/FileDao.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file.dao;
+
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import com.ffii.core.dao.HibernateDao;
+import com.ffii.tbms.file.File;
+
+@Repository
+public class FileDao extends HibernateDao {
+
+ @Autowired
+ public FileDao(SessionFactory sessionFactory) {
+ super(File.class);
+ setSessionFactory(sessionFactory);
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/file/dao/FileRefDao.java b/src/main/java/com/ffii/tls/file/dao/FileRefDao.java
new file mode 100644
index 0000000..5087654
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/dao/FileRefDao.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file.dao;
+
+import org.hibernate.SessionFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import com.ffii.core.dao.HibernateDao;
+import com.ffii.tbms.file.FileRef;
+
+@Repository
+public class FileRefDao extends HibernateDao {
+
+ @Autowired
+ public FileRefDao(SessionFactory sessionFactory) {
+ super(FileRef.class);
+ setSessionFactory(sessionFactory);
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/file/service/FileService.java b/src/main/java/com/ffii/tls/file/service/FileService.java
new file mode 100644
index 0000000..2dfbc13
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/service/FileService.java
@@ -0,0 +1,292 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file.service;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.ffii.core.dao.JdbcDao;
+import com.ffii.core.utils.FileUtils;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.web.AbstractService;
+import com.ffii.core.web.view.AbstractView;
+import com.ffii.tbms.file.File;
+import com.ffii.tbms.file.FileBlob;
+import com.ffii.tbms.file.FileRef;
+import com.ffii.tbms.file.dao.FileBlobDao;
+import com.ffii.tbms.file.dao.FileDao;
+import com.ffii.tbms.file.dao.FileRefDao;
+
+/**
+ * @author Patrick
+ */
+@Service
+public class FileService extends AbstractService {
+
+ @Autowired
+ private JdbcDao jdbcDao;
+
+ @Autowired
+ private FileDao fileDao;
+
+ @Autowired
+ private FileBlobDao fileBlobDao;
+
+ @Autowired
+ private FileRefDao fileRefDao;
+
+ /**
+ * Save File, FileBlob, and FileRef in one go
+ *
+ * @param filename
+ * the filename, cannot be null
+ * @param description
+ * optional File description
+ * @param refType
+ * the File reference type, should not be empty
+ * @param refId
+ * mandatory, use 0 if N/A
+ * @param refCode
+ * optional
+ * @param bytes
+ * the File byte array
+ *
+ * @return File ID
+ */
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveFile(String filename, String description, String refType, int refId, String refCode, byte[] bytes) {
+ File file = new File();
+ file.setFilename(filename);
+ file.setDescription(description);
+ file.setMimetype(FileUtils.guessMimetype(filename));
+ file.setFilesize(bytes.length);
+ file.setSkey(RandomStringUtils.randomAlphanumeric(16));
+
+ FileBlob fileBlob = new FileBlob();
+ fileBlob.setBytes(bytes);
+
+ FileRef fileRef = new FileRef();
+ fileRef.setRefId(refId);
+ fileRef.setRefType(refType);
+ fileRef.setRefCode(refCode);
+
+ // try to get width and height if mimetype is png or jpeg
+ if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype()) || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) {
+ BufferedImage image;
+ try {
+ image = ImageIO.read(new ByteArrayInputStream(bytes));
+ if (image != null) {
+ file.setImageWidth(image.getWidth());
+ file.setImageHeight(image.getHeight());
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ // save File
+ saveFile(file);
+
+ // save FileBlob
+ fileBlob.setFileId(file.getId());
+ saveFileBlob(fileBlob);
+
+ // save FileRef
+ fileRef.setFileId(file.getId());
+ saveFileRef(fileRef);
+
+ return file.getId();
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveFile(File instance) {
+ return fileDao.saveOrUpdate(instance);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveFileBlob(FileBlob instance) {
+ return fileBlobDao.saveOrUpdate(instance);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public Integer saveFileRef(FileRef instance) {
+ return fileRefDao.saveOrUpdate(instance);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public File findFileByIdAndKey(int id, String skey) {
+ return fileDao.findByQuery("from com.ffii.tbms.file.File f where f.id = ? and f.skey = ?", id, skey);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public File findFileById(int id) {
+ return fileDao.findByQuery("from com.ffii.tbms.file.File f where f.id = ? ", id);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public FileRef findFileRefByTypeAndId(String refType, int refId) {
+ return fileRefDao.findByQuery("from com.ffii.tbms.file.FileRef where refType = ? and refId = ?", refType, refId);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public FileRef findFileRefByfileId(int fileId) {
+ return fileRefDao.findByQuery("from com.ffii.tbms.file.FileRef where fileId = ?", fileId);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public FileBlob findFileBlobByFileId(int fileId) {
+ return fileBlobDao.findByQuery("from com.ffii.tbms.file.FileBlob fb where fb.fileId = ?", fileId);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public boolean isFileExists(int id, String skey) {
+
+ String sql = "SELECT"
+ + " COUNT(1)"
+ + " FROM files f"
+ + " WHERE f.deleted = 0"
+ + " AND f.id = :id"
+ + " AND f.skey = :skey";
+
+ int count = jdbcDao.queryForInt(sql, MapUtils.toHashMap("id", id, "skey", skey));
+
+ return (count > 0);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public List> searchFiles(Map args) {
+
+ StringBuilder sql = new StringBuilder("SELECT"
+ + " f.id,"
+ + " f.filename,"
+ + " f.filesize,"
+ + " f.mimetype, "
+ + " f.skey,"
+ + " fr.refId,"
+ + " fr.refType,"
+ + " f.created,"
+ + " f.imageWidth,"
+ + " f.imageHeight,"
+ + " u.fullname AS createdByName,"
+ + " f.description"
+ + " FROM files f"
+ + " LEFT JOIN files_ref fr ON f.id = fr.fileId"
+ + " LEFT JOIN users u ON f.createdBy = u.id"
+ + " WHERE f.deleted = 0 AND fr.deleted =0 ");
+
+ if (args.containsKey("filename")) sql.append(" AND f.filename = :filename");
+
+ if (args.containsKey("refType")) sql.append(" AND fr.refType = :refType");
+
+ if (args.containsKey("refId")) sql.append(" AND fr.refId = :refId");
+
+ if (args.containsKey("startDate")) sql.append(" AND f.created >= :startDate");
+
+ if (args.containsKey("endDate")) sql.append(" AND f.created < :endDate");
+
+ if (args.containsKey("fileId")) sql.append(" AND f.id = :fileId");
+
+
+ sql.append(" ORDER BY f.created DESC");
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true)
+ public List> searchFilesFromDocument(Map args) {
+
+ StringBuilder sql = new StringBuilder("SELECT"
+ + " f.id,"
+ + " f.filename,"
+ + " f.filesize,"
+ + " f.mimetype,"
+ + " f.skey,"
+ + " fr.refId,"
+ + " fr.refType,"
+ + " f.created,"
+ + " u.fullname AS createdByName,"
+ + " f.description"
+ + " FROM files_ref fr"
+ + " LEFT JOIN files f ON f.id = fr.fileId"
+ + " LEFT JOIN users u ON f.createdBy = u.id"
+ + " LEFT JOIN document_ref dr ON dr.documentId = fr.refId AND fr.refType = 'document'"
+ + " WHERE 1 = 1"
+ + " AND f.deleted = 0"
+ + " AND fr.deleted = 0"
+ + " AND dr.deleted = 0");
+
+ if (args != null) {
+ if (args.containsKey("filename")) sql.append(" AND f.filename = :filename");
+ if (args.containsKey("refType")) sql.append(" AND fr.refType = :refType");
+ if (args.containsKey("refId")) sql.append(" AND fr.refId = :refId");
+ if (args.containsKey("startDate")) sql.append(" AND f.created >= :startDate");
+ if (args.containsKey("endDate")) sql.append(" AND f.created < :endDate");
+ if (args.containsKey("documentRefType")) sql.append(" AND dr.refType = :documentRefType");
+ if (args.containsKey("documentRefId")) sql.append(" AND dr.refId = :documentRefId");
+ }
+
+ sql.append(" GROUP BY f.id"
+ + " ORDER BY f.created DESC");
+
+ return jdbcDao.queryForList(sql.toString(), args);
+ }
+
+ /**
+ * Delete FileRef
by fileId
, refId
, refType
, and skey
+ */
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public void deleteFile(Integer fileId, Integer refId, String refType, String skey) {
+ Map args = MapUtils.toHashMap("fileId", fileId, "refId", refId, "refType", refType, "skey", skey);
+
+ jdbcDao.executeUpdate("DELETE FROM files_ref"
+ + " WHERE fileId = :fileId"
+ + " AND refId = :refId"
+ + " AND refType = :refType"
+ + " AND EXISTS (SELECT 1 FROM files WHERE id = files_ref.fileId AND skey = :skey)",
+ args);
+ jdbcDao.executeUpdate("DELETE FROM files_blob WHERE id = :fileId ",args);
+ }
+
+ /**
+ * Scheduled daily job
+ *
+ * Delete all orphan (without any FileRef
) File
s and FileBlob
s
+ */
+ @Scheduled(cron = "0 0 0 * * ?") // everyday at 00:00:00
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
+ public void deleteOrphanFiles() {
+ jdbcDao.executeUpdate("DELETE FROM files_blob WHERE NOT EXISTS (SELECT 1 FROM files_ref WHERE deleted = 0 AND fileId = files_blob.fileId)");
+ jdbcDao.executeUpdate("DELETE FROM files WHERE NOT EXISTS (SELECT 1 FROM files_ref WHERE deleted = 0 AND fileId = files.id)");
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> getCustImageList() {
+ return jdbcDao.queryForList(" Select cp.* From cust_photo cp ", null);
+ }
+
+ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true)
+ public List> getOrderImageList() {
+ return jdbcDao.queryForList(" Select op.* From order_photo cp ", null);
+ }
+
+
+
+}
diff --git a/src/main/java/com/ffii/tls/file/web/DeleteFileController.java b/src/main/java/com/ffii/tls/file/web/DeleteFileController.java
new file mode 100644
index 0000000..6d2a978
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/web/DeleteFileController.java
@@ -0,0 +1,103 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file.web;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.web.ServletRequestUtils;
+import com.ffii.core.web.AbstractController;
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.file.FileRefType;
+import com.ffii.tbms.file.service.FileService;
+import com.ffii.tbms.log.LogUtils;
+import com.ffii.tbms.order.service.OrderService;
+
+@Controller
+@RequestMapping(value = "/file")
+public class DeleteFileController extends AbstractController {
+
+ @Autowired
+ private FileService fileService;
+
+ @Autowired
+ private OrderService orderService;
+
+ @RequestMapping(value = "/delete", method = { RequestMethod.GET, RequestMethod.POST })
+ public String delete(Model model,
+ @RequestParam Integer fileId, @RequestParam Integer refId, @RequestParam String refType, @RequestParam String skey)
+ throws Exception {
+
+ fileService.deleteFile(fileId, refId, refType, skey);
+
+ if(refType.equals(FileRefType.MEASUREMENT_SHEET)){
+ orderService.log(LogUtils.ACTION_DELETE,
+ refId,
+ MapUtils.toHashMap("fileId", fileId)
+ );
+ }else if(refType.equals(FileRefType.ORDER)){
+ orderService.log(LogUtils.ACTION_DELETE,
+ refId,
+ MapUtils.toHashMap("photoFileId", fileId)
+ );
+ }
+
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/multi-delete", method = { RequestMethod.GET, RequestMethod.POST })
+ public String multiDelete(Model model, final HttpServletRequest request)
+ throws Exception {
+
+ @SuppressWarnings("unchecked")
+ List> deleteRecord =
+ (List>)ServletRequestUtils.getJsonListParameter(request, "deleteRecord");
+
+ for(Map record: deleteRecord){
+ int fileId = NumberUtils.intValue(record.get("fileId"));
+ int refId = NumberUtils.intValue(record.get("refId"));
+ String refType = record.get("refType").toString();
+ String skey = record.get("skey").toString();
+
+ fileService.deleteFile(fileId, refId, refType, skey);
+
+ if(refType.equals(FileRefType.MEASUREMENT_SHEET)){
+ orderService.log(LogUtils.ACTION_DELETE,
+ refId,
+ MapUtils.toHashMap("fileId", fileId)
+ );
+ }else if(refType.equals(FileRefType.ORDER)){
+ orderService.log(LogUtils.ACTION_DELETE,
+ refId,
+ MapUtils.toHashMap("photoFileId", fileId)
+ );
+ }
+ }
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+
+ return JsonView.NAME;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/file/web/DownloadFileController.java b/src/main/java/com/ffii/tls/file/web/DownloadFileController.java
new file mode 100644
index 0000000..d74e26c
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/web/DownloadFileController.java
@@ -0,0 +1,203 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file.web;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.text.DecimalFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import javax.imageio.ImageIO;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import com.ffii.core.utils.DateUtils;
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.StringUtils;
+import com.ffii.core.web.AbstractController;
+import com.ffii.tbms.file.File;
+import com.ffii.tbms.file.FileBlob;
+import com.ffii.tbms.file.service.FileService;
+import java.awt.image.BufferedImage;
+
+@Controller
+@RequestMapping(value = "/file")
+public class DownloadFileController extends AbstractController {
+
+ private boolean ZIP_NUMBERER = false;
+
+ @Autowired
+ private FileService fileService;
+
+ @RequestMapping(value = "/dl/{id}/{skey}/{filename}", method = RequestMethod.GET)
+ public void download(HttpServletResponse response, @RequestParam(defaultValue = "false") boolean dl,
+ @PathVariable int id, @PathVariable String skey) throws Exception {
+
+ File file = fileService.findFileByIdAndKey(id, skey);
+
+ if (file != null) {
+ FileBlob fileBlob = fileService.findFileBlobByFileId(id);
+
+ response.reset();
+ response.setContentType(file.getMimetype());
+ response.setContentLength((int) file.getFilesize());
+ response.setHeader("Content-Transfer-Encoding", "binary");
+ response.setHeader("Content-Disposition",
+ String.format("%s; filename=\"%s\"", dl ? "attachment" : "inline", URLEncoder.encode(file.getFilename(), "UTF-8")));
+
+ ServletOutputStream out = response.getOutputStream();
+
+ try {
+ out.write(fileBlob.getBytes());
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ logger.warn(e.getMessage());
+ } finally {
+ out.close();
+ }
+ } else {
+ logger.info("*** 400 BAD REQUEST ***");
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ }
+ }
+
+ @RequestMapping(value = "/dlz", produces = "application/zip", method = RequestMethod.GET)
+ public void downloadAsZip(HttpServletResponse response,
+ @RequestParam String filename, @RequestParam String ids, @RequestParam String skeys) throws IOException {
+
+ filename += DateUtils.formatDate(new Date(), "_yyyy_MM_dd_HHmm", "") + ".zip";
+
+ response.setHeader("Content-Transfer-Encoding", "binary");
+ response.setHeader("Content-Disposition",
+ String.format("%s; filename=\"%s\"", "attachment", response.encodeURL(filename)));
+
+ DecimalFormat df00 = new DecimalFormat("00");
+
+ ZipOutputStream zipOutputStream = new ZipOutputStream(response.getOutputStream());
+
+ String[] idsA = StringUtils.split(ids, ',');
+ String[] skeysA = StringUtils.split(skeys, ',');
+
+ int filePrefixIdx = 1;
+
+ for (int i = 0; i < idsA.length; i++) {
+ int id = NumberUtils.intValue(idsA[i]);
+ String skey = skeysA[i];
+
+ File file = fileService.findFileByIdAndKey(id, skey);
+
+ if (file == null) {
+ logger.warn("*** file is null, id = " + id + ", skey = " + skey);
+ } else {
+ FileBlob fileBlob = fileService.findFileBlobByFileId(id);
+
+ if (fileBlob == null) {
+ logger.warn("*** fileBlob is null, id = " + id + ", skey = " + skey);
+ } else {
+ zipOutputStream.putNextEntry(new ZipEntry(ZIP_NUMBERER ? (df00.format(filePrefixIdx++) + "_" + file.getFilename())
+ : file.getFilename()));
+ zipOutputStream.write(fileBlob.getBytes());
+ zipOutputStream.closeEntry();
+ }
+ }
+ }
+ zipOutputStream.flush();
+ zipOutputStream.close();
+ }
+
+ @RequestMapping(value = "/thumbnail/{id}/{skey}/{filename}", method = RequestMethod.GET)
+ public void thumbnail(HttpServletResponse response, Model model, @RequestParam(defaultValue = "false") boolean dl,
+ @PathVariable int id, @PathVariable String skey) throws IOException {
+
+ File file = fileService.findFileByIdAndKey(id, skey);
+
+ if (file != null) {
+ FileBlob fileBlob = fileService.findFileBlobByFileId(id);
+
+ response.reset();
+ response.setContentType(file.getMimetype());
+ // response.setContentLength((int) file.getFilesize());
+ response.setHeader("Content-Transfer-Encoding", "binary");
+ response.setHeader("Content-Disposition", String.format("%s; filename=\"%s\"", dl ? "attachment" : "inline",
+ response.encodeURL(file.getFilename())));
+
+ int limit = 500;
+ BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
+ int width = image.getWidth();
+ int height = image.getHeight();
+ if (width > height) {
+ if (width > limit) {
+ height = height * limit / width;
+ width = limit;
+ }
+ } else {
+ if (height > limit) {
+ width = width * limit / height;
+ height = limit;
+ }
+ }
+ image = scale(image, width, height);
+ ByteArrayOutputStream tmp = new ByteArrayOutputStream();
+ ImageIO.write(image, "jpg", tmp);
+ tmp.close();
+ response.setContentLength((int) tmp.size());
+
+ ServletOutputStream out = response.getOutputStream();
+
+ try {
+ out.write(tmp.toByteArray());
+ out.flush();
+ out.close();
+ } catch (IOException e) {
+ logger.warn(e.getMessage());
+ } finally {
+ out.close();
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ }
+ } else {
+ logger.info("*** 400 BAD REQUEST ***");
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+ }
+
+ static BufferedImage scale(BufferedImage originalImage, int w, int h) {
+ BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
+ int x, y;
+ int ww = originalImage.getWidth();
+ int hh = originalImage.getHeight();
+ for (x = 0; x < w; x++) {
+ for (y = 0; y < h; y++) {
+ int col = originalImage.getRGB(x * ww / w, y * hh / h);
+ img.setRGB(x, y, col);
+ }
+ }
+ return img;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/file/web/FileJsonController.java b/src/main/java/com/ffii/tls/file/web/FileJsonController.java
new file mode 100644
index 0000000..7f37881
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/web/FileJsonController.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright 2019 2Fi Business Solutions Ltd.
+ *
+ *
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file.web;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.ServletRequestUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import com.ffii.core.utils.CriteriaUtils;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.StringUtils;
+import com.ffii.core.web.AbstractController;
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.file.File;
+import com.ffii.tbms.file.service.FileService;
+import com.ffii.tbms.order.Order;
+import com.ffii.tbms.order.service.OrderService;
+
+@Controller
+@RequestMapping(value = "/file")
+public class FileJsonController extends AbstractController {
+
+ @Autowired
+ private FileService fileService;
+
+ @Autowired
+ private OrderService orderService;
+
+ @RequestMapping(value = "/list.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String listJson(Model model, HttpServletRequest request, @RequestParam(required = false) String refType,
+ @RequestParam(required = false) Integer refId) throws ServletRequestBindingException {
+
+ Map args = new HashMap();
+ CriteriaUtils.addDate(request, args, "startDate");
+ CriteriaUtils.addDateTo(request, args, "endDate");
+
+ List> records = new ArrayList<>();
+
+ String documentRefType = ServletRequestUtils.getStringParameter(request, "documentRefType");
+ Integer documentRefId = ServletRequestUtils.getIntParameter(request, "documentRefId");
+ if (StringUtils.isNotEmpty(documentRefType) && documentRefId != null) {
+ args.put("documentRefType", documentRefType);
+ args.put("documentRefId", documentRefId);
+
+ records = fileService.searchFilesFromDocument(args);
+ } else if (StringUtils.isNotEmpty(refType) && refId != null) {
+ args.put(Params.REF_TYPE, refType);
+ args.put(Params.REF_ID, refId);
+
+ records = fileService.searchFiles(args);
+ }
+
+ model.addAttribute(Params.RECORDS, records);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/list-order.json", method = { RequestMethod.GET, RequestMethod.POST })
+ public String listOrderJson(Model model, HttpServletRequest request, @RequestParam(required = false) String refType,
+ @RequestParam(required = false) Integer refId) throws ServletRequestBindingException {
+
+ Map args = new HashMap();
+ List> records = new ArrayList<>();
+ args.put("refType", refType);
+ args.put("refId", refId);
+ records = fileService.searchFiles(args);
+
+ Order order = orderService.find(refId);
+
+ if(order.getFileId() != null)
+ records.addAll(fileService.searchFiles(MapUtils.toHashMap("fileId",order.getFileId())));
+
+ model.addAttribute(Params.RECORDS, records);
+ model.addAttribute("title", order.getType()+order.getCode());
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/update", method = RequestMethod.POST)
+ public String update(Model model, HttpServletResponse response,
+ @RequestParam int id, @RequestParam String skey, @RequestParam String filename,
+ @RequestParam(defaultValue = StringUtils.EMPTY) String description) {
+
+ File file = fileService.findFileByIdAndKey(id, skey);
+
+ if (file != null) {
+
+ file.setFilename(filename);
+ file.setDescription(description);
+ fileService.saveFile(file);
+
+ model.addAttribute(Params.SUCCESS, Boolean.TRUE);
+ } else {
+ model.addAttribute(Params.SUCCESS, Boolean.FALSE);
+ }
+
+ return JsonView.NAME;
+ }
+
+}
diff --git a/src/main/java/com/ffii/tls/file/web/UploadFileController.java b/src/main/java/com/ffii/tls/file/web/UploadFileController.java
new file mode 100644
index 0000000..f070eaf
--- /dev/null
+++ b/src/main/java/com/ffii/tls/file/web/UploadFileController.java
@@ -0,0 +1,513 @@
+/*******************************************************************************
+ * Copyright 2Fi Business Solutions Ltd.
+ *
+ * This code is copyrighted. Under no circumstances should any party, people,
+ * or organization should redistribute any portions of this code in any form,
+ * either verbatim or through electronic media, to any third parties, unless
+ * under explicit written permission by 2Fi Business Solutions Ltd.
+ ******************************************************************************/
+package com.ffii.tbms.file.web;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.Map;
+
+import javax.imageio.ImageIO;
+import javax.servlet.http.HttpServletRequest;
+
+import com.fasterxml.jackson.databind.JsonSerializable;
+import com.fasterxml.jackson.databind.ser.std.JsonValueSerializer;
+import com.ffii.core.setting.service.SettingsService;
+import com.ffii.core.utils.FileUtils;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.NumberUtils;
+import com.ffii.core.utils.Params;
+import com.ffii.core.utils.StringUtils;
+import com.ffii.core.utils.web.ServletRequestUtils;
+import com.ffii.core.web.AbstractController;
+import com.ffii.core.web.view.AbstractView;
+import com.ffii.core.web.view.json.JsonView;
+import com.ffii.tbms.common.MaterialBand;
+import com.ffii.tbms.common.service.MaterialService;
+import com.ffii.tbms.file.File;
+import com.ffii.tbms.file.FileBlob;
+import com.ffii.tbms.file.FileRef;
+import com.ffii.tbms.file.FileRefType;
+import com.ffii.tbms.file.service.FileService;
+import com.ffii.tbms.log.LogUtils;
+import com.ffii.tbms.order.Order;
+import com.ffii.tbms.order.service.OrderService;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * Upload File Controller
+ *
+ * @see File
+ * @see FileBlob
+ * @see FileRef
+ */
+@Controller
+@RequestMapping(value = "/file")
+public class UploadFileController extends AbstractController {
+
+ @Autowired
+ private SettingsService settingsService;
+
+ @Autowired
+ private FileService fileService;
+
+ @Autowired
+ private OrderService orderService;
+
+ @Autowired
+ private MaterialService materialService;
+
+ private static final boolean OVERWRITE_SAME_FILENAME = false;
+
+ private static final int DEFAULT_UPLOAD_MAX_FILE_SIZE_MB = 50;
+
+ @RequestMapping(value = "/ul", method = RequestMethod.POST)
+ public String uploadFile(HttpServletRequest request, Model model,
+ @RequestParam int refId, @RequestParam String refType, @RequestParam(defaultValue = StringUtils.EMPTY) String refCode,
+ @RequestParam MultipartFile multipartFile) throws Exception {
+
+ // get file upload max file size setting
+ int uploadMaxFileSize = settingsService.getInt("FILE.upload.maxFileSize", DEFAULT_UPLOAD_MAX_FILE_SIZE_MB) * 1024 * 1024;
+
+ Boolean success = Boolean.TRUE;
+
+ // only proceed if multipartFile is not null, and has file size
+ if (multipartFile != null && multipartFile.getSize() > 0 && multipartFile.getSize() <= uploadMaxFileSize) {
+
+ // DEBUG LOG
+ logger.info("multipartFile.getSize() = " + multipartFile.getSize());
+
+ File file = new File();
+ file.setFilename(multipartFile.getOriginalFilename());
+ // file.setMimetype(multipartFile.getContentType());
+ file.setMimetype(FileUtils.guessMimetype(file.getFilename()));
+ file.setFilesize(multipartFile.getSize());
+ file.setDescription(ServletRequestUtils.getTrimmedStringParameter(request, "description"));
+
+ FileBlob fileBlob = new FileBlob();
+ fileBlob.setBytes(multipartFile.getBytes());
+
+ FileRef fileRef = new FileRef();
+ fileRef.setRefId(refId);
+ fileRef.setRefType(refType);
+ fileRef.setRefCode(refCode);
+
+ // get height and width if mimetype is png or jpeg
+ if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype()) || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) {
+ BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
+ if (image != null) {
+ file.setImageHeight(image.getHeight());
+ file.setImageWidth(image.getWidth());
+ }
+ }
+
+ if (OVERWRITE_SAME_FILENAME) {
+ // search for existing file(s) with the same refType, refId, and filename
+ List> existingFiles = fileService.searchFiles(
+ MapUtils.toHashMap("refType", refType, "refId", refId, "filename", file.getFilename()));
+
+ // delete them if found
+ for (Map existingFile : existingFiles) {
+ fileService.deleteFile((Integer) existingFile.get("id"), (Integer) existingFile.get("refId"),
+ (String) existingFile.get("refType"), (String) existingFile.get("skey"));
+ }
+ }
+
+ // create UserFile
+ file.setSkey(RandomStringUtils.randomAlphanumeric(16));
+ fileService.saveFile(file);
+
+ // create UserFileBlob
+ fileBlob.setFileId(file.getId());
+ fileService.saveFileBlob(fileBlob);
+
+ // create UserFileRef
+ fileRef.setFileId(file.getId());
+ fileService.saveFileRef(fileRef);
+
+ if(refType.equals(FileRefType.ORDER)){
+ orderService.log(LogUtils.ACTION_CREATE,
+ refId,
+ MapUtils.toHashMap("photoFileId", file.getId(), "filename",file.getFilename()));
+ }
+
+ } else {
+ success = Boolean.FALSE;
+
+ // if not success, return msg to client
+ model.addAttribute(Params.MSG, getMessageSourceAccessor().getMessage("Upload Failed"));
+ }
+
+ model.addAttribute(Params.SUCCESS, success);
+
+ return JsonView.NAME;
+ }
+
+
+ @RequestMapping(value = "/files/ul", method = RequestMethod.POST)
+ public String uploadFiles(HttpServletRequest request, Model model,@RequestParam String refType,
+ @RequestParam int refId, @RequestParam(defaultValue = StringUtils.EMPTY) String refCode,
+ @RequestParam List multipartFiles) throws ServletRequestBindingException, IOException {
+
+ // get file upload max file size setting
+ int uploadMaxFileSize = settingsService.getInt("FILE.upload.maxFileSize", DEFAULT_UPLOAD_MAX_FILE_SIZE_MB) * 1024 * 1024;
+
+ for(MultipartFile multipartFile: multipartFiles){
+
+ if (multipartFile != null && multipartFile.getSize() > 0 && multipartFile.getSize() <= uploadMaxFileSize) {
+ logger.info("file size : "+ multipartFile.getSize());
+ File file = new File();
+ file.setFilename(multipartFile.getOriginalFilename());
+ // file.setMimetype(multipartFile.getContentType());
+ file.setMimetype(FileUtils.guessMimetype(file.getFilename()));
+ file.setFilesize(multipartFile.getSize());
+ file.setDescription(ServletRequestUtils.getTrimmedStringParameter(request, "description"));
+
+ FileBlob fileBlob = new FileBlob();
+ fileBlob.setBytes(multipartFile.getBytes());
+
+ FileRef fileRef = new FileRef();
+ fileRef.setRefId(refId);
+ fileRef.setRefType(refType);
+ fileRef.setRefCode(refCode);
+
+ // get height and width if mimetype is png or jpeg
+ if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype()) || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) {
+ BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
+ if (image != null) {
+ file.setImageHeight(image.getHeight());
+ file.setImageWidth(image.getWidth());
+ }
+ }
+
+ if (OVERWRITE_SAME_FILENAME) {
+ // search for existing file(s) with the same refType, refId, and filename
+ List> existingFiles = fileService.searchFiles(
+ MapUtils.toHashMap("refType", refType, "refId", refId, "filename", file.getFilename()));
+
+ // delete them if found
+ for (Map existingFile : existingFiles) {
+ fileService.deleteFile((Integer) existingFile.get("id"), (Integer) existingFile.get("refId"),
+ (String) existingFile.get("refType"), (String) existingFile.get("skey"));
+ }
+ }
+
+ // create UserFile
+ file.setSkey(RandomStringUtils.randomAlphanumeric(16));
+ fileService.saveFile(file);
+
+ // create UserFileBlob
+ fileBlob.setFileId(file.getId());
+ fileService.saveFileBlob(fileBlob);
+
+ // create UserFileRef
+ fileRef.setFileId(file.getId());
+ fileService.saveFileRef(fileRef);
+
+ if(refType.equals(FileRefType.ORDER)){
+ orderService.log(LogUtils.ACTION_CREATE,
+ refId,
+ MapUtils.toHashMap("photoFileId", file.getId(), "filename",file.getFilename()));
+ }
+ logger.info("file done : "+ multipartFile.getName());
+
+ }else{
+ model.addAttribute(Params.SUCCESS, false);
+ return JsonView.NAME;
+ }
+
+ }
+
+ model.addAttribute(Params.SUCCESS, true);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/measurement-sheet/ul", method = RequestMethod.POST)
+ public String uploadMeasurementSheetFile(HttpServletRequest request, Model model,
+ @RequestParam int orderId, @RequestParam MultipartFile multipartFile) throws Exception {
+
+ Order order = orderService.find(orderId);
+ String refType = FileRefType.MEASUREMENT_SHEET;
+ String refCode = order.getCustId() + "";
+ Integer refId = orderId;
+
+ // get file upload max file size setting
+ int uploadMaxFileSize = settingsService.getInt("FILE.upload.maxFileSize", DEFAULT_UPLOAD_MAX_FILE_SIZE_MB) * 1024 * 1024;
+
+ Boolean success = Boolean.TRUE;
+
+ // only proceed if multipartFile is not null, and has file size
+ if (multipartFile != null && multipartFile.getSize() > 0 && multipartFile.getSize() <= uploadMaxFileSize) {
+
+ // DEBUG LOG
+ logger.info("multipartFile.getSize() = " + multipartFile.getSize());
+
+ File file = new File();
+ file.setFilename(multipartFile.getOriginalFilename());
+ // file.setMimetype(multipartFile.getContentType());
+ file.setMimetype(FileUtils.guessMimetype(file.getFilename()));
+ file.setFilesize(multipartFile.getSize());
+ file.setDescription(ServletRequestUtils.getTrimmedStringParameter(request, "description"));
+
+ FileBlob fileBlob = new FileBlob();
+ fileBlob.setBytes(multipartFile.getBytes());
+
+ FileRef fileRef = new FileRef();
+ fileRef.setRefId(refId);
+ fileRef.setRefType(refType);
+ fileRef.setRefCode(refCode);
+
+ // get height and width if mimetype is png or jpeg
+ if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype()) || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) {
+ BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
+ if (image != null) {
+ file.setImageHeight(image.getHeight());
+ file.setImageWidth(image.getWidth());
+ }
+ }
+
+ if (OVERWRITE_SAME_FILENAME) {
+ // search for existing file(s) with the same refType, refId, and filename
+ List> existingFiles = fileService.searchFiles(
+ MapUtils.toHashMap("refType", refType, "refId", refId, "filename", file.getFilename()));
+
+ // delete them if found
+ for (Map existingFile : existingFiles) {
+ fileService.deleteFile((Integer) existingFile.get("id"), (Integer) existingFile.get("refId"),
+ (String) existingFile.get("refType"), (String) existingFile.get("skey"));
+ }
+ }
+
+ // create UserFile
+ file.setSkey(RandomStringUtils.randomAlphanumeric(16));
+ fileService.saveFile(file);
+
+ // create UserFileBlob
+ fileBlob.setFileId(file.getId());
+ fileService.saveFileBlob(fileBlob);
+
+ // create UserFileRef
+ fileRef.setFileId(file.getId());
+ fileService.saveFileRef(fileRef);
+
+ order.setFileId(file.getId());
+ orderService.saveOrUpdate(order);
+ orderService.log(LogUtils.ACTION_CREATE,
+ order.getId(),
+ MapUtils.toHashMap("fileId", order.getFileId(), "filename", file.getFilename()));
+ } else {
+ success = Boolean.FALSE;
+
+ // if not success, return msg to client
+ model.addAttribute(Params.MSG, getMessageSourceAccessor().getMessage("Upload Failed"));
+ }
+
+ model.addAttribute(Params.SUCCESS, success);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/band/icon/ul", method = { RequestMethod.GET, RequestMethod.POST })
+ public String uploadBandIconFile(HttpServletRequest request, Model model,
+ @RequestParam int bandId, @RequestParam MultipartFile multipartFile) throws Exception {
+
+ MaterialBand band = materialService.findBand(bandId);
+ String refType = FileRefType.BAND_ICON;
+ Integer refId = bandId;
+
+ // get file upload max file size setting
+ int uploadMaxFileSize = settingsService.getInt("FILE.upload.maxFileSize", DEFAULT_UPLOAD_MAX_FILE_SIZE_MB) * 1024 * 1024;
+
+ Boolean success = Boolean.TRUE;
+
+ // only proceed if multipartFile is not null, and has file size
+ if (multipartFile != null && multipartFile.getSize() > 0 && multipartFile.getSize() <= uploadMaxFileSize) {
+
+ // DEBUG LOG
+ logger.info("multipartFile.getSize() = " + multipartFile.getSize());
+
+ File file = new File();
+ file.setFilename(multipartFile.getOriginalFilename());
+ // file.setMimetype(multipartFile.getContentType());
+ file.setMimetype(FileUtils.guessMimetype(file.getFilename()));
+ file.setFilesize(multipartFile.getSize());
+ file.setDescription(ServletRequestUtils.getTrimmedStringParameter(request, "description"));
+
+ FileBlob fileBlob = new FileBlob();
+ fileBlob.setBytes(multipartFile.getBytes());
+
+ FileRef fileRef = new FileRef();
+ fileRef.setRefId(refId);
+ fileRef.setRefType(refType);
+
+ // get height and width if mimetype is png or jpeg
+ if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype()) || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) {
+ BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
+ if (image != null) {
+ file.setImageHeight(image.getHeight());
+ file.setImageWidth(image.getWidth());
+ }
+ }
+
+
+ // search for existing file(s) with the same refType, refId, and filename
+ List> existingFiles = fileService.searchFiles(
+ MapUtils.toHashMap("refType", refType, "refId", refId));
+
+ // delete them if found
+ for (Map existingFile : existingFiles) {
+ fileService.deleteFile((Integer) existingFile.get("id"), (Integer) existingFile.get("refId"),
+ (String) existingFile.get("refType"), (String) existingFile.get("skey"));
+ }
+
+
+ // create UserFile
+ file.setSkey(RandomStringUtils.randomAlphanumeric(16));
+ fileService.saveFile(file);
+
+ // create UserFileBlob
+ fileBlob.setFileId(file.getId());
+ fileService.saveFileBlob(fileBlob);
+
+ // create UserFileRef
+ fileRef.setFileId(file.getId());
+ fileService.saveFileRef(fileRef);
+
+ band.setFileId(file.getId());
+ materialService.saveOrUpdateBand(band);
+ } else {
+ success = Boolean.FALSE;
+
+ // if not success, return msg to client
+ model.addAttribute(Params.MSG, getMessageSourceAccessor().getMessage("Upload Failed"));
+ }
+
+ model.addAttribute(Params.SUCCESS, success);
+
+ return JsonView.NAME;
+ }
+
+ @RequestMapping(value = "/load-image", method = { RequestMethod.GET, RequestMethod.POST })
+ private String loadImage(){
+
+ List> custList = fileService.getCustImageList();
+ List> orderList = fileService.getOrderImageList();
+
+ String ignored = "";
+
+ for(Map item: custList ){
+ int custId = NumberUtils.intValue(item.get("id"), 0);
+ if(custId == 0) {
+ ignored = ignored+"\n CustId:"+item.get("id")+", photo:"+item.get("cust_photo");
+ continue;
+ }
+
+ String[] photos = item.get("cust_photo").toString().trim().split("::");
+ for(String filename: photos){
+ String path = this.getClass().getClassLoader().getResource("").getPath() + "../report/"+filename;
+ if(path != null){
+ java.io.File file = new java.io.File(path);
+ try {
+ createFile(custId, FileRefType.CUSTOMER, file, filename);
+ } catch (IOException e) {
+ ignored = ignored+"\n CustId:"+item.get("id")+", photo:"+filename;
+ }
+ }
+ }
+ }
+
+ for(Map item: orderList){
+ int orderId = NumberUtils.intValue(item.get("id"), 0);
+ if(orderId == 0) {
+ ignored = ignored+"\n OrderId:"+item.get("id")+", photo:"+item.get("order_photo");
+ continue;
+ }
+
+ String[] ophotos = item.get("order_photo").toString().trim().split("::");
+ for(String filename: ophotos){
+ String path = this.getClass().getClassLoader().getResource("").getPath() + "../report/"+filename;
+ if(path != null){
+ java.io.File file = new java.io.File(path);
+ try {
+ createFile(orderId, FileRefType.ORDER, file, filename);
+ } catch (IOException e) {
+ ignored = ignored+"\n OrderId:"+item.get("id")+", photo:"+filename;
+ }
+ }
+ }
+ }
+
+ logger.info(ignored);
+
+ return JsonView.NAME;
+ }
+
+ private void createFile(int refId, String refType, java.io.File f, String filename) throws IOException{
+ byte[] fileContent = Files.readAllBytes(f.toPath());
+
+ File file = new File();
+ file.setFilename(filename);
+ // file.setMimetype(multipartFile.getContentType());
+ file.setMimetype(FileUtils.guessMimetype(filename));
+ file.setFilesize(fileContent.length);
+
+ FileBlob fileBlob = new FileBlob();
+ fileBlob.setBytes(fileContent);
+
+ FileRef fileRef = new FileRef();
+ fileRef.setRefId(refId);
+ fileRef.setRefType(refType);
+
+ // get height and width if mimetype is png or jpeg
+ if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype()) || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) {
+ BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileBlob.getBytes()));
+ if (image != null) {
+ file.setImageHeight(image.getHeight());
+ file.setImageWidth(image.getWidth());
+ }
+ }
+
+ // search for existing file(s) with the same refType, refId, and filename
+ List> existingFiles = fileService.searchFiles(
+ MapUtils.toHashMap("refType", refType, "refId", refId));
+
+ // delete them if found
+ for (Map existingFile : existingFiles) {
+ fileService.deleteFile((Integer) existingFile.get("id"), (Integer) existingFile.get("refId"),
+ (String) existingFile.get("refType"), (String) existingFile.get("skey"));
+ }
+
+ // create UserFile
+ file.setSkey(RandomStringUtils.randomAlphanumeric(16));
+ fileService.saveFile(file);
+
+ // create UserFileBlob
+ fileBlob.setFileId(file.getId());
+ fileService.saveFileBlob(fileBlob);
+
+ // create UserFileRef
+ fileRef.setFileId(file.getId());
+ fileService.saveFileRef(fileRef);
+ }
+
+
+}
diff --git a/src/main/java/com/ffii/tls/log/LogUtils.java b/src/main/java/com/ffii/tls/log/LogUtils.java
new file mode 100644
index 0000000..1b266f4
--- /dev/null
+++ b/src/main/java/com/ffii/tls/log/LogUtils.java
@@ -0,0 +1,83 @@
+package com.ffii.tbms.log;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.ffii.core.utils.MapUtils;
+import com.ffii.core.utils.NumberUtils;
+
+
+public class LogUtils{
+
+ public static final String DIFF = "diff";
+ public static final String ADD = "add";
+ public static final String DEL = "del";
+ public static final String SAME = "same";
+
+ public static final String ACTION_CREATE = "Create";
+ public static final String ACTION_UPDATE = "Update";
+ public static final String ACTION_DELETE = "Delete";
+
+ public static Map objectToMap(Object obj){
+ ObjectMapper mapper = new ObjectMapper();
+ Map map = mapper.convertValue(obj, Map.class);
+ return map;
+ }
+
+ public static Map