/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////
package org.projectforge.address;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.projectforge.access.AccessType;
import org.projectforge.access.OperationType;
import org.projectforge.common.DateHelper;
import org.projectforge.common.DateHolder;
import org.projectforge.common.NumberHelper;
import org.projectforge.core.BaseDao;
import org.projectforge.core.BaseSearchFilter;
import org.projectforge.core.Configuration;
import org.projectforge.core.ConfigurationParam;
import org.projectforge.core.QueryFilter;
import org.projectforge.task.TaskDO;
import org.projectforge.task.TaskDao;
import org.projectforge.user.PFUserDO;
/**
*
* @author Kai Reinhard (k.reinhard@micromata.de)
*
*/
public class AddressDao extends BaseDao<AddressDO>
{
private static final DateFormat V_CARD_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(AddressDao.class);
private Configuration configuration;
private TaskDao taskDao;
public void setConfiguration(final Configuration configuration)
{
this.configuration = configuration;
}
public void setTaskDao(final TaskDao taskDao)
{
this.taskDao = taskDao;
}
private PersonalAddressDao personalAddressDao;
public void setPersonalAddressDao(final PersonalAddressDao personalAddressDao)
{
this.personalAddressDao = personalAddressDao;
}
public PersonalAddressDao getPersonalAddressDao()
{
return personalAddressDao;
}
public AddressDao()
{
super(AddressDO.class);
}
/**
* Addresses will be assigned to a default task.
*/
public Integer getDefaultTaskId()
{
return configuration.getTaskIdValue(ConfigurationParam.DEFAULT_TASK_ID_4_ADDRESSES);
}
public List<Locale> getUsedCommunicationLanguages()
{
@SuppressWarnings("unchecked")
final List<Locale> list = getHibernateTemplate()
.find(
"select distinct a.communicationLanguage from AddressDO a where deleted=false and a.communicationLanguage is not null order by a.communicationLanguage");
return list;
}
/**
* Get the newest address entries (by time of creation).
* @return
* @see #getNewestMax()
*/
public List<AddressDO> getNewest(final BaseSearchFilter filter)
{
final QueryFilter queryFilter = new QueryFilter();
queryFilter.addOrder(Order.desc("created"));
if (filter.getMaxRows() > 0) {
queryFilter.setMaxResults(filter.getMaxRows());
}
return getList(queryFilter);
}
@Override
public List<AddressDO> getList(final BaseSearchFilter filter)
{
final AddressFilter myFilter;
if (filter instanceof AddressFilter) {
myFilter = (AddressFilter) filter;
} else {
myFilter = new AddressFilter(filter);
}
final QueryFilter queryFilter = new QueryFilter(myFilter);
if (StringUtils.isBlank(myFilter.getSearchString()) == true) {
if (myFilter.isDeleted() == false) {
if (myFilter.isNewest() == true) {
return getNewest(myFilter);
}
if (myFilter.isMyFavorites() == true) {
// Show only favorites.
return personalAddressDao.getMyAddresses();
}
}
} else {
if (StringUtils.isNumeric(filter.getSearchString()) == true) {
myFilter.setSearchString("*" + myFilter.getSearchString() + "*");
}
}
if (myFilter.isFilter() == true) {
// Proceed contact status:
// Use filter only for non deleted entries:
if (myFilter.isActive() == true
|| myFilter.isNonActive() == true
|| myFilter.isUninteresting() == true
|| myFilter.isDeparted() == true
|| myFilter.isPersonaIngrata() == true) {
final Collection<ContactStatus> col = new ArrayList<ContactStatus>();
if (myFilter.isActive() == true) {
col.add(ContactStatus.ACTIVE);
}
if (myFilter.isNonActive() == true) {
col.add(ContactStatus.NON_ACTIVE);
}
if (myFilter.isUninteresting() == true) {
col.add(ContactStatus.UNINTERESTING);
}
if (myFilter.isDeparted() == true) {
col.add(ContactStatus.DEPARTED);
}
if (myFilter.isPersonaIngrata() == true) {
col.add(ContactStatus.PERSONA_INGRATA);
}
queryFilter.add(Restrictions.in("contactStatus", col));
}
// Proceed address status:
// Use filter only for non deleted books:
if (myFilter.isUptodate() == true || myFilter.isOutdated() == true || myFilter.isLeaved() == true) {
final Collection<AddressStatus> col = new ArrayList<AddressStatus>();
if (myFilter.isUptodate() == true) {
col.add(AddressStatus.UPTODATE);
}
if (myFilter.isOutdated() == true) {
col.add(AddressStatus.OUTDATED);
}
if (myFilter.isLeaved() == true) {
col.add(AddressStatus.LEAVED);
}
queryFilter.add(Restrictions.in("addressStatus", col));
}
}
queryFilter.addOrder(Order.asc("name"));
final List<AddressDO> result = getList(queryFilter);
if (myFilter.isDoublets() == true) {
final HashSet<String> fullnames = new HashSet<String>();
final HashSet<String> doubletFullnames = new HashSet<String>();
for (final AddressDO address : result) {
final String fullname = getNormalizedFullname(address);
if (fullnames.contains(fullname) == true) {
doubletFullnames.add(fullname);
}
fullnames.add(fullname);
}
final List<AddressDO> doublets = new LinkedList<AddressDO>();
for (final AddressDO address : result) {
if (doubletFullnames.contains(getNormalizedFullname(address)) == true) {
doublets.add(address);
}
}
return doublets;
}
return result;
}
private String getNormalizedFullname(final AddressDO address)
{
final StringBuilder builder = new StringBuilder();
if (address.getFirstName() != null) {
builder.append(address.getFirstName().toLowerCase().trim());
}
if (address.getName() != null) {
builder.append(address.getName().toLowerCase().trim());
}
return builder.toString();
}
/**
* @param address
* @param taskId If null, then task will be set to null;
* @see BaseDao#getOrLoad(Integer)
*/
public void setTask(final AddressDO address, final Integer taskId)
{
final TaskDO task = taskDao.getOrLoad(taskId);
address.setTask(task);
}
/**
* return Always true, no generic select access needed for address objects.
* @see org.projectforge.core.BaseDao#hasSelectAccess()
*/
@Override
public boolean hasSelectAccess(final PFUserDO user, final boolean throwException)
{
return true;
}
private void beforeUpdateOrSave(final AddressDO address)
{
if (address != null && address.getTaskId() == null) {
setTask(address, getDefaultTaskId());
}
}
/**
* @see org.projectforge.core.BaseDao#hasAccess(Object, OperationType)
*/
@Override
public boolean hasAccess(final PFUserDO user, final AddressDO obj, final AddressDO oldObj, final OperationType operationType,
final boolean throwException)
{
beforeUpdateOrSave(obj);
return accessChecker.hasPermission(user, obj.getTaskId(), AccessType.TASKS, operationType, throwException);
}
/**
* @see org.projectforge.core.BaseDao#hasUpdateAccess(Object, Object)
*/
@Override
public boolean hasUpdateAccess(final PFUserDO user, final AddressDO obj, final AddressDO dbObj, final boolean throwException)
{
Validate.notNull(dbObj);
Validate.notNull(obj);
beforeUpdateOrSave(obj);
Validate.notNull(dbObj.getTaskId());
Validate.notNull(obj.getTaskId());
if (accessChecker.hasPermission(user, obj.getTaskId(), AccessType.TASKS, OperationType.UPDATE, throwException) == false) {
return false;
}
if (dbObj.getTaskId().equals(obj.getTaskId()) == false) {
// User moves the object to another task:
if (accessChecker.hasPermission(user, obj.getTaskId(), AccessType.TASKS, OperationType.INSERT, throwException) == false) {
// Inserting of object under new task not allowed.
return false;
}
if (accessChecker.hasPermission(user, dbObj.getTaskId(), AccessType.TASKS, OperationType.DELETE, throwException) == false) {
// Deleting of object under old task not allowed.
return false;
}
}
return true;
}
/**
* Get the birthdays of address entries.
* @param fromDate Search for birthdays from given date (ignoring the year).
* @param toDate Search for birthdays until given date (ignoring the year).
* @param max Maximum number of result entries.
* @param all If false, only the birthdays of favorites will be returned.
* @return The entries are ordered by date of year and name.
*/
public Set<BirthdayAddress> getBirthdays(final Date fromDate, final Date toDate, final int max, final boolean all)
{
final QueryFilter filter = new QueryFilter();
filter.add(Restrictions.isNotNull("birthday"));
final List<AddressDO> list = getList(filter);
// Uses not Collections.sort because every comparison needs Calendar.getDayOfYear().
final Set<BirthdayAddress> set = new TreeSet<BirthdayAddress>();
final Set<Integer> favorites = getFavorites();
final DateHolder from = new DateHolder(fromDate);
final DateHolder to = new DateHolder(toDate);
DateHolder dh;
final int fromMonth = from.getMonth();
final int fromDayOfMonth = from.getDayOfMonth();
final int toMonth = to.getMonth();
final int toDayOfMonth = to.getDayOfMonth();
for (final AddressDO address : list) {
if (all == false && favorites.contains(address.getId()) == false) {
// Address is not a favorite address, so ignore it.
continue;
}
dh = new DateHolder(address.getBirthday());
final int month = dh.getMonth();
final int dayOfMonth = dh.getDayOfMonth();
if (DateHelper.dateOfYearBetween(month, dayOfMonth, fromMonth, fromDayOfMonth, toMonth, toDayOfMonth) == false) {
continue;
}
final BirthdayAddress ba = new BirthdayAddress(address);
if (favorites.contains(address.getId()) == true) {
ba.setFavorite(true);
}
set.add(ba);
}
return set;
}
public List<PersonalAddressDO> getFavoriteVCards()
{
final List<PersonalAddressDO> list = personalAddressDao.getList();
final List<PersonalAddressDO> result = new ArrayList<PersonalAddressDO>();
if (CollectionUtils.isNotEmpty(list) == true) {
for (final PersonalAddressDO entry : list) {
if (entry.isFavoriteCard() == true) {
result.add(entry);
}
}
}
return result;
}
public Set<Integer> getFavorites()
{
final List<PersonalAddressDO> list = personalAddressDao.getList();
final Set<Integer> result = new HashSet<Integer>();
if (CollectionUtils.isNotEmpty(list) == true) {
for (final PersonalAddressDO entry : list) {
if (entry.isFavoriteCard() == true) {
result.add(entry.getAddressId());
}
}
}
return result;
}
public void exportFavoriteVCards(final Writer out, final List<PersonalAddressDO> favorites)
{
log.info("Exporting personal AddressBook.");
final PrintWriter pw = new PrintWriter(out);
for (final PersonalAddressDO entry : favorites) {
if (entry.isFavoriteCard() == false) {
// Entry is not marks as vCard-Entry.
continue;
}
final AddressDO addressDO = entry.getAddress();
exportVCard(pw, addressDO);
}
pw.flush();
}
/**
* Exports a single vcard for the given addressDO
* @param pw
* @param addressDO
* @return
*/
public void exportVCard(final PrintWriter pw, final AddressDO addressDO)
{
if (log.isDebugEnabled() == true) {
log.debug("Exporting vCard for addressDo : " + addressDO != null ? addressDO.getId() : null);
}
pw.println("BEGIN:VCARD");
pw.println("VERSION:3.0");
pw.print("N:");
out(pw, addressDO.getName());
pw.print(';');
out(pw, addressDO.getFirstName());
pw.print(";;");
out(pw, addressDO.getTitle());
pw.println(";");
print(pw, "FN:", getFullName(addressDO));
if (isGiven(addressDO.getOrganization()) == true || isGiven(addressDO.getDivision()) == true) {
pw.print("ORG:");
out(pw, addressDO.getOrganization());
pw.print(';');
if (isGiven(addressDO.getDivision()) == true) {
out(pw, addressDO.getDivision());
}
pw.println();
}
print(pw, "TITLE:", addressDO.getPositionText());
print(pw, "EMAIL;type=INTERNET;type=WORK;type=pref:", addressDO.getEmail());
print(pw, "EMAIL;type=INTERNET;type=HOME;type=pref:", addressDO.getPrivateEmail());
print(pw, "TEL;type=WORK;type=pref:", addressDO.getBusinessPhone());
print(pw, "TEL;TYPE=CELL:", addressDO.getMobilePhone());
print(pw, "TEL;type=WORK;type=FAX:", addressDO.getFax());
print(pw, "TEL;TYPE=HOME:", addressDO.getPrivatePhone());
print(pw, "TEL;TYPE=HOME;type=CELL:", addressDO.getPrivateMobilePhone());
if (isGiven(addressDO.getAddressText()) == true || isGiven(addressDO.getCity()) == true || isGiven(addressDO.getZipCode()) == true) {
pw.print("ADR;TYPE=WORK:;;");
out(pw, addressDO.getAddressText());
pw.print(';');
out(pw, addressDO.getCity());
pw.print(";;");
out(pw, addressDO.getZipCode());
pw.print(';');
out(pw, addressDO.getCountry());
pw.println();
}
if (isGiven(addressDO.getPrivateAddressText()) == true
|| isGiven(addressDO.getPrivateCity()) == true
|| isGiven(addressDO.getPrivateZipCode()) == true) {
pw.print("ADR;TYPE=HOME:;;");
out(pw, addressDO.getPrivateAddressText());
pw.print(';');
out(pw, addressDO.getPrivateCity());
pw.print(";;");
out(pw, addressDO.getPrivateZipCode());
pw.print(";");
pw.println();
}
print(pw, "URL;type=pref:", addressDO.getWebsite());
if (addressDO.getBirthday() != null) {
print(pw, "BDAY;value=date:", V_CARD_DATE_FORMAT.format(addressDO.getBirthday()));
}
if (isGiven(addressDO.getComment()) == true) {
print(pw, "NOTE:", addressDO.getComment() + "\\nCLASS: WORK");
} else {
print(pw, "NOTE:", "CLASS: WORK");
}
// pw.println("TZ:+00:00");
pw.println("CATEGORIES:ProjectForge");
pw.print("UID:U");
pw.println(addressDO.getId());
pw.println("END:VCARD");
pw.println();
// Unused: addressDO.getState();
}
/**
* Used by vCard export for field 'FN' (full name). Concatenates first name, last name and title.
* @return
*/
public String getFullName(final AddressDO a)
{
final StringBuffer buf = new StringBuffer();
boolean space = false;
if (isGiven(a.getName()) == true) {
buf.append(a.getName());
space = true;
}
if (isGiven(a.getFirstName()) == true) {
if (space == true) {
buf.append(' ');
} else {
space = true;
}
buf.append(a.getFirstName());
}
if (isGiven(a.getTitle()) == true) {
if (space == true) {
buf.append(' ');
} else {
space = true;
}
buf.append(a.getTitle());
}
return buf.toString();
}
public List<PersonalAddressDO> getFavoritePhoneEntries()
{
final List<PersonalAddressDO> list = personalAddressDao.getList();
final List<PersonalAddressDO> result = new ArrayList<PersonalAddressDO>();
if (CollectionUtils.isNotEmpty(list) == true) {
for (final PersonalAddressDO entry : list) {
if (entry.isFavoriteBusinessPhone() == true
|| entry.isFavoriteFax() == true
|| entry.isFavoriteMobilePhone() == true
|| entry.isFavoritePrivatePhone() == true) {
result.add(entry);
}
}
}
return result;
}
/**
* Throws UserException, if for example the phone list is empty.
*/
public void exportFavoritePhoneList(final Writer out, final List<PersonalAddressDO> favorites)
{
log.info("Exporting phone list");
final PrintWriter pw = new PrintWriter(out);
pw.println("\"Name\",\"Phone number\"");
for (final PersonalAddressDO entry : favorites) {
final AddressDO address = entry.getAddress();
String number = address.getBusinessPhone();
if (entry.isFavoriteBusinessPhone() == true && StringUtils.isNotBlank(number)) {
appendPhoneEntry(pw, address, "", number);
}
number = address.getFax();
if (entry.isFavoriteFax() == true && StringUtils.isNotBlank(number)) {
appendPhoneEntry(pw, address, "fax", number);
}
number = address.getMobilePhone();
if (entry.isFavoriteMobilePhone() == true && StringUtils.isNotBlank(number)) {
appendPhoneEntry(pw, address, "mobil", number);
}
number = address.getPrivateMobilePhone();
if (entry.isFavoritePrivateMobilePhone() == true && StringUtils.isNotBlank(number)) {
final String str = StringUtils.isNotBlank(address.getMobilePhone()) == true ? "mobil privat" : "mobil";
appendPhoneEntry(pw, address, str, number);
}
number = address.getPrivatePhone();
if (entry.isFavoritePrivatePhone() == true && StringUtils.isNotBlank(number)) {
appendPhoneEntry(pw, address, "privat", number);
}
}
pw.flush();
}
private void print(final PrintWriter pw, final String key, final String value)
{
if (isGiven(value) == false) {
return;
}
pw.print(key);
out(pw, value);
pw.println();
}
/**
* Simply calls StringUtils.defaultString(String) and replaces: "\r" -> "", "\n" -> "\\n", "," -> "\\,", ":" -> "\\:" and print the
* resulted string into given PrintWriter (without newline).
* @param str
* @see StringUtils#defaultString(String)
*/
private void out(final PrintWriter pw, final String str)
{
final String s = StringUtils.defaultString(str);
boolean cr = false;
for (int i = 0; i < s.length(); i++) {
final char ch = s.charAt(i);
if (ch == ':') {
pw.print("\\:");
} else if (ch == ',') {
pw.print("\\,");
} else if (ch == ';') {
pw.print("\\;");
} else if (ch == '\r') {
pw.print("\\n");
cr = true;
continue;
} else if (ch == '\n') {
if (cr == false) {
// Print only \n if not already done by previous \r.
pw.print("\\n");
}
} else {
pw.print(ch);
}
cr = false;
}
}
/**
* Simply call StringUtils.isNotBlank(String)
* @param str
* @return
* @see StringUtils#isNotBlank(String)
*/
private boolean isGiven(final String str)
{
return StringUtils.isNotBlank(str);
}
private void appendPhoneEntry(final PrintWriter pw, final AddressDO address, final String suffix, final String number)
{
if (isGiven(number) == false) {
// Do nothing, number is empty.
return;
}
final String no = NumberHelper
.extractPhonenumber(number, configuration.getStringValue(ConfigurationParam.DEFAULT_COUNTRY_PHONE_PREFIX));
final String name = address.getName();
pw.print("\"");
if (StringUtils.isNotEmpty(name)) {
pw.print(name);
}
final String firstName = address.getFirstName();
if (StringUtils.isNotBlank(firstName)) {
if (StringUtils.isNotBlank(name)) {
pw.print(", ");
}
pw.print(firstName);
}
if (StringUtils.isNotEmpty(suffix)) {
pw.print(' ');
pw.print(suffix);
}
pw.print("\",\"");
pw.println(no + "\"");
}
@Override
public AddressDO newInstance()
{
return new AddressDO();
}
/**
* @see org.projectforge.core.BaseDao#useOwnCriteriaCacheRegion()
*/
@Override
protected boolean useOwnCriteriaCacheRegion()
{
return true;
}
}