/*
 * WebTop Services is a Web Application framework developed by Sonicle S.r.l.
 * Copyright (C) 2014 Sonicle S.r.l.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY SONICLE, SONICLE DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Sonicle S.r.l. at email address sonicle@sonicle.com
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * Sonicle logo and Sonicle copyright notice. If the display of the logo is not
 * reasonably feasible for technical reasons, the Appropriate Legal Notices must
 * display the words "Copyright (C) 2014 Sonicle S.r.l.".
 */
package com.sonicle.webtop.core;

import com.sonicle.commons.EnumUtils;
import com.sonicle.commons.InternetAddressUtils;
import com.sonicle.commons.LangUtils;
import com.sonicle.commons.beans.VirtualAddress;
import com.sonicle.commons.db.DbUtils;
import com.sonicle.commons.web.json.CompositeId;
import com.sonicle.security.auth.directory.AbstractDirectory;
import com.sonicle.webtop.core.app.CoreAdminManifest;
import com.sonicle.webtop.core.app.RunContext;
import com.sonicle.webtop.core.app.CoreManifest;
import com.sonicle.webtop.core.app.OTPManager;
import com.sonicle.webtop.core.app.ServiceManager;
import com.sonicle.webtop.core.app.SessionManager;
import com.sonicle.webtop.core.app.SettingsManager;
import com.sonicle.webtop.core.app.WebTopManager;
import com.sonicle.webtop.core.app.WT;
import com.sonicle.webtop.core.app.WebTopApp;
import com.sonicle.webtop.core.app.pbx.PbxProvider;
import com.sonicle.webtop.core.app.pbx.NethVoice;
import com.sonicle.webtop.core.app.provider.RecipientsProviderBase;
import com.sonicle.webtop.core.app.sms.SmsProvider;
import com.sonicle.webtop.core.bol.VCausal;
import com.sonicle.webtop.core.bol.OActivity;
import com.sonicle.webtop.core.bol.OAutosave;
import com.sonicle.webtop.core.bol.OCausal;
import com.sonicle.webtop.core.bol.ODomain;
import com.sonicle.webtop.core.bol.OGroup;
import com.sonicle.webtop.core.bol.OIMChat;
import com.sonicle.webtop.core.bol.OIMMessage;
import com.sonicle.webtop.core.bol.OMasterData;
import com.sonicle.webtop.core.bol.OSnoozedReminder;
import com.sonicle.webtop.core.bol.ORolePermission;
import com.sonicle.webtop.core.bol.OServiceStoreEntry;
import com.sonicle.webtop.core.bol.OShare;
import com.sonicle.webtop.core.bol.OShareData;
import com.sonicle.webtop.core.bol.OUser;
import com.sonicle.webtop.core.bol.js.JsSimple;
import com.sonicle.webtop.core.model.ServicePermission;
import com.sonicle.webtop.core.model.ServiceSharePermission;
import com.sonicle.webtop.core.model.SharePermsElements;
import com.sonicle.webtop.core.model.SharePermsFolder;
import com.sonicle.webtop.core.model.IncomingShareRoot;
import com.sonicle.webtop.core.model.Recipient;
import com.sonicle.webtop.core.bol.model.Role;
import com.sonicle.webtop.core.bol.model.Sharing;
import com.sonicle.webtop.core.model.SharePermsRoot;
import com.sonicle.webtop.core.bol.model.SyncDevice;
import com.sonicle.webtop.core.bol.model.UserOptionsServiceData;
import com.sonicle.webtop.core.dal.ActivityDAO;
import com.sonicle.webtop.core.dal.AutosaveDAO;
import com.sonicle.webtop.core.dal.CausalDAO;
import com.sonicle.webtop.core.dal.DAOException;
import com.sonicle.webtop.core.dal.IMChatDAO;
import com.sonicle.webtop.core.dal.IMMessageDAO;
import com.sonicle.webtop.core.dal.MasterDataDAO;
import com.sonicle.webtop.core.dal.SnoozedReminderDAO;
import com.sonicle.webtop.core.dal.RolePermissionDAO;
import com.sonicle.webtop.core.dal.ServiceStoreEntryDAO;
import com.sonicle.webtop.core.dal.ShareDAO;
import com.sonicle.webtop.core.dal.ShareDataDAO;
import com.sonicle.webtop.core.dal.UserDAO;
import com.sonicle.webtop.core.model.Activity;
import com.sonicle.webtop.core.model.Causal;
import com.sonicle.webtop.core.model.CausalExt;
import com.sonicle.webtop.core.model.IMChat;
import com.sonicle.webtop.core.model.IMMessage;
import com.sonicle.webtop.core.model.MasterData;
import com.sonicle.webtop.core.model.MasterDataLookup;
import com.sonicle.webtop.core.model.PublicImage;
import com.sonicle.webtop.core.model.RecipientFieldType;
import com.sonicle.webtop.core.sdk.AuthException;
import com.sonicle.webtop.core.sdk.BaseManager;
import com.sonicle.webtop.core.sdk.ReminderInApp;
import com.sonicle.webtop.core.sdk.ServiceManifest;
import com.sonicle.webtop.core.sdk.UserProfile;
import com.sonicle.webtop.core.sdk.UserProfileId;
import com.sonicle.webtop.core.sdk.WTException;
import com.sonicle.webtop.core.sdk.interfaces.IRecipientsProvidersSource;
import com.sonicle.webtop.core.util.ZPushManager;
import java.net.URI;
import java.net.URISyntaxException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.mail.internet.InternetAddress;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.slf4j.Logger;

/**
 *
 * @author malbinola
 */
public class CoreManager extends BaseManager {
	private static final Logger logger = WT.getLogger(CoreManager.class);
	private WebTopApp wta = null;
	
	public static final String RECIPIENT_PROVIDER_AUTO_SOURCE_ID = "auto";
	public static final String RECIPIENT_PROVIDER_WEBTOP_SOURCE_ID = "webtop";

	
	private final HashSet<String> cacheReady = new HashSet<>();
	private final ArrayList<String> cacheAllowedServices = new ArrayList<>();
	private final LinkedHashMap<String, RecipientsProviderBase> cacheProfileRecipientsProvider = new LinkedHashMap<>();
	
	private PbxProvider pbx=null;
	private SmsProvider sms=null;
	
	public CoreManager(WebTopApp wta, boolean fastInit, UserProfileId targetProfileId) {
		super(fastInit, targetProfileId);
		this.wta = wta;
		
		if(!fastInit) {
			//initAllowedServices();
		}
	}
	
	/*
	private void initAllowedServices() {
		synchronized(cacheAllowedServices) {
			cacheAllowedServices.addAll(doListAllowedServices());
			cacheReady.add("cacheAllowedServices");
		}
	}
	*/
	
	private void initPbx() {
		if (pbx==null) {
			UserProfileId pid=getTargetProfileId();
			CoreServiceSettings css = new CoreServiceSettings(CoreManifest.ID, pid.getDomainId());		
			String provider=css.getPbxProvider();
			if (provider!=null) {
				pbx=PbxProvider.getInstance(provider, pid);
			}
		}
	}

	private void initSms() {
		if (sms==null) {
			UserProfileId pid=getTargetProfileId();
			CoreServiceSettings css = new CoreServiceSettings(CoreManifest.ID, pid.getDomainId());		
			String provider=css.getSmsProvider();
			if (provider!=null) {
				sms=SmsProvider.getInstance(getLocale(), provider, pid);
			}
		}
	}

	public ServiceManager getServiceManager() {
		ensureCallerService(SERVICE_ID, "getServiceManager");
		return wta.getServiceManager();
	}
	
	public OTPManager getOTPManager() {
		ensureCallerService(SERVICE_ID, "getOTPManager");
		return wta.getOTPManager();
	}
	
	public List<JsSimple> listThemes() throws WTException {
		ArrayList<JsSimple> items = new ArrayList<>();
		//TODO: gestire i temi dinamicamente
		items.add(new JsSimple("crisp", "Crisp"));
		//items.add(new JsSimple("crisp-touch", "Crisp Touch"));
		//items.add(new JsSimple("triton", "Triton"));
		items.add(new JsSimple("neptune", "Neptune"));
		//items.add(new JsSimple("neptune-touch", "Neptune Touch"));
		items.add(new JsSimple("neptune", "Neptune"));
		//items.add(new JsSimple("aria", "Aria"));
		items.add(new JsSimple("classic", "Classic"));
		items.add(new JsSimple("gray", "Gray"));
		return items;
	}
	
	public List<JsSimple> listLayouts() throws WTException {
		return Arrays.asList(
			new JsSimple("default", lookupResource(getLocale(), "layout.default")),
			new JsSimple("compact", lookupResource(getLocale(), "layout.compact"))
		);
	}
	
	public List<JsSimple> listLAFs() throws WTException {
		ArrayList<JsSimple> items = new ArrayList<>();
		//TODO: gestire i look&feel (lafs) dinamicamente
		items.add(new JsSimple("default", WT.getPlatformName()));
		return items;
	}
	
	/**
	 * Returns target domain's authentication Directory.
	 * @return The authentication Directory
	 * @throws WTException 
	 */
	public AbstractDirectory getAuthDirectory() throws WTException {
		// We can skip right check, an error will be returned here in getDomain()
		ODomain domain = getDomain();
		return (domain != null) ? wta.getWebTopManager().getAuthDirectory(domain.getDirUri()) : null;
	}
	
	public String getAuthDirectoryScheme() throws WTException {
		AbstractDirectory dir = getAuthDirectory();
		return (dir != null) ? dir.getScheme() : null;
	}
	
	@Deprecated
	public AbstractDirectory getAuthDirectory(String domainId) throws WTException {
		ODomain domain = getDomain(domainId);
		if(domain == null) throw new WTException("Domain not found [{0}]", domainId);
		
		return getAuthDirectory(domain);
	}
	
	@Deprecated
	public AbstractDirectory getAuthDirectory(ODomain domain) throws WTException {
		if (RunContext.isSysAdmin()) {
			return wta.getWebTopManager().getAuthDirectory(domain.getDirUri());
		} else {
			ensureUserDomain(domain.getDomainId());
			return wta.getWebTopManager().getAuthDirectory(domain.getDirUri());
		}
	}
	
	public List<ODomain> listDomains(boolean enabledOnly) throws WTException {
		WebTopManager wtmgr = wta.getWebTopManager();
		
		if(RunContext.isSysAdmin()) {
			return wtmgr.listDomains(enabledOnly);
		} else {
			ODomain domain = wtmgr.getDomain(RunContext.getRunProfileId().getDomain());
			return domain.getEnabled() ? Arrays.asList(domain) : new ArrayList<ODomain>();
		}
	}
	
	public ODomain getDomain() throws WTException {
		return getDomain(getTargetProfileId().getDomainId());
	}
	
	public ODomain getDomain(String domainId) throws WTException {
		WebTopManager wtmgr = wta.getWebTopManager();
		
		if(RunContext.isSysAdmin()) {
			return wtmgr.getDomain(domainId);
		} else {
			ensureUserDomain(domainId);
			return wtmgr.getDomain(domainId);
		}
	}
	
	public List<PublicImage> listDomainPublicImages() throws WTException {
		WebTopManager wtmgr = wta.getWebTopManager();
		String domainId = getTargetProfileId().getDomainId();
		
		try {
			return wtmgr.listDomainPublicImages(domainId);
		} catch(Exception ex) {
			throw new WTException(ex, "Unable to list domain's public images [{0}]", domainId);
		}
	}
	
	/**
	 * Lists domain real roles (those defined as indipendent role).
	 * Target domain ID is taken from manager targetProfile's domain ID.
	 * @return The role list.
	 * @throws WTException If something go wrong.
	 */
	public List<Role> listRoles() throws WTException {
		String domainId = getTargetProfileId().getDomainId();
		
		//TODO: permettere la chiamata per l'admin di dominio (admin@dominio)
		ensureUserDomain(domainId);
		
		WebTopManager wtmgr = wta.getWebTopManager();
		try {
			return wtmgr.listRoles(domainId);
		} catch(Exception ex) {
			throw new WTException(ex, "Unable to list roles [{0}]", domainId);
		}
	}
	
	/**
	 * Lists domain users roles (those coming from a user).
	 * Target domain ID is taken from manager targetProfile's domain ID.
	 * @return The role list.
	 * @throws WTException If something go wrong.
	 */
	public List<Role> listUsersRoles() throws WTException {
		String domainId = getTargetProfileId().getDomainId();
		
		//TODO: permettere la chiamata per l'admin di dominio (admin@dominio)
		ensureUserDomain(domainId);
		
		WebTopManager wtmgr = wta.getWebTopManager();
		try {
			return wtmgr.listUsersRoles(domainId);
		} catch(Exception ex) {
			throw new WTException(ex, "Unable to list users roles [{0}]", domainId);
		}
	}
	
	/**
	 * Lists domain groups roles (those coming from a group).
	 * Target domain ID is taken from manager targetProfile's domain ID.
	 * @return The role list.
	 * @throws WTException If something go wrong.
	 */
	public List<Role> listGroupsRoles() throws WTException {
		String domainId = getTargetProfileId().getDomainId();
		
		//TODO: permettere la chiamata per l'admin di dominio (admin@dominio)
		ensureUserDomain(domainId);
		
		WebTopManager wtmgr = wta.getWebTopManager();
		try {
			return wtmgr.listGroupsRoles(domainId);
		} catch(Exception ex) {
			throw new WTException(ex, "Unable to list groups roles [{0}]", domainId);
		}
	}
	
	public List<OUser> listUsers(boolean enabledOnly) throws WTException {
		String domainId = getTargetProfileId().getDomainId();
		WebTopManager wtmgr = wta.getWebTopManager();
		
		try {
			return wtmgr.listUsers(domainId, enabledOnly);
		} catch(Exception ex) {
			throw new WTException(ex, "Unable to list users [{0}]", domainId);
		}
	}
	
	public List<UserProfileId> listUserIdsByEmail(String emailAddress) throws WTException {
		WebTopManager wtmgr = wta.getWebTopManager();
		return wtmgr.listUserProfileIdsByEmail(emailAddress);
	}
	
	public OUser getUser() throws WTException {
		return getUser(getTargetProfileId());
	}
	
	public OUser getUser(UserProfileId pid) throws WTException {
		WebTopManager wtmgr = wta.getWebTopManager();
		
		if(RunContext.isSysAdmin()) {
			return wtmgr.getUser(pid);
		} else {
			//TODO: permettere la chiamata per l'admin di dominio (admin@dominio)
			ensureUserDomain(pid.getDomainId());
			return wtmgr.getUser(pid);
		}
	}
	
	public UserProfile.Data getUserData() throws WTException {
		return wta.getWebTopManager().userData(getTargetProfileId());
	}
	
	public String getUserUid(UserProfileId pid) throws WTException {
		return wta.getWebTopManager().userToUid(pid,false);
	}
	
	public UserProfile.PersonalInfo getUserPersonalInfo() throws WTException {
		return getUserPersonalInfo(getTargetProfileId());
	}
	
	public UserProfile.PersonalInfo getUserPersonalInfo(UserProfileId pid) throws WTException {
		WebTopManager wtmgr = wta.getWebTopManager();
		
		if(RunContext.isSysAdmin()) {
			return wtmgr.getUserPersonalInfo(pid);
		} else {
			ensureUserDomain(pid.getDomainId());
			return wtmgr.getUserPersonalInfo(pid);
		}
	}
	
	public boolean updateUserDisplayName(String displayName) throws WTException {
		WebTopManager wtmgr = wta.getWebTopManager();
		
		if(RunContext.isSysAdmin()) {
			return wtmgr.updateUserDisplayName(getTargetProfileId(), displayName);
		} else {
			//TODO: permettere la chiamata per l'admin di dominio (admin@dominio)
			ensureUser();
			return wtmgr.updateUserDisplayName(getTargetProfileId(), displayName);
		}
	}
	
	public boolean updateUserPersonalInfo(UserProfile.PersonalInfo userPersonalInfo) throws WTException {
		WebTopManager wtmgr = wta.getWebTopManager();
		
		if(RunContext.isSysAdmin()) {
			return wtmgr.updateUserPersonalInfo(getTargetProfileId(), userPersonalInfo);
		} else {
			//TODO: permettere la chiamata per l'admin di dominio (admin@dominio)
			ensureProfile();
			return wtmgr.updateUserPersonalInfo(getTargetProfileId(), userPersonalInfo);
		}
	}
	
	public void updateUserPassword(char[] oldPassword, char[] newPassword) throws WTException {
		WebTopManager wtmgr = wta.getWebTopManager();
		
		try {
			ensureProfile();
			if (oldPassword == null) throw new WTException("Old password must be provided");
			wtmgr.updateUserPassword(getTargetProfileId(), oldPassword, newPassword);
		} catch(Throwable t) {
			throw new WTException(t, "Unable to change user password [{0}]", getTargetProfileId().toString());
		}
	}
	
	public void cleanUserProfileCache() {
		ensureCallerService(SERVICE_ID, "cleanupUserProfileCache");
		wta.getWebTopManager().cleanUserProfileCache(getTargetProfileId());
	}
	
	public List<OGroup> listGroups() throws WTException {
		String domainId = getTargetProfileId().getDomainId();
		WebTopManager wtmgr = wta.getWebTopManager();
		
		try {
			return wtmgr.listGroups(domainId);
		} catch(Exception ex) {
			throw new WTException(ex, "Unable to list groups [{0}]", domainId);
		}
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	public List<String> listWTInstalledServices() {
		ServiceManager svcm = wta.getServiceManager();
		return svcm.listRegisteredServices();
	}
	
	public List<ServicePermission> listServicePermissions(String serviceId) throws WTException {
		ServiceManager svcm = wta.getServiceManager();
		List<ServicePermission> perms = svcm.getDeclaredPermissions(serviceId);
		if (perms == null) throw new WTException("Service not found [{0}]", serviceId);
		return perms;
	}
	
	public Set<String> listAllowedServices() {
		LinkedHashSet<String> ids = new LinkedHashSet<>();
		
		UserProfileId targetPid = getTargetProfileId();
		ServiceManager svcm = wta.getServiceManager();
		for (String id : svcm.listRegisteredServices()) {
			if (RunContext.isPermitted(true, targetPid, SERVICE_ID, "SERVICE", "ACCESS", id)) ids.add(id);
		}
		return ids;
	}
	
	/**
	 * Returns UserOption services data for current target user.
	 */
	public List<UserOptionsServiceData> getAllowedUserOptionServices() {
		ArrayList<UserOptionsServiceData> items = new ArrayList<>();
		
		UserProfileId targetPid = getTargetProfileId();
		ServiceManager svcm = wta.getServiceManager();
		for (String serviceId : svcm.listUserOptionServices()) {
			if (RunContext.isPermitted(true, targetPid, SERVICE_ID, "SERVICE", "ACCESS", serviceId)) {
				UserOptionsServiceData uosd = new UserOptionsServiceData(svcm.getManifest(serviceId));
				uosd.name = wta.lookupResource(serviceId, getLocale(), CoreLocaleKey.SERVICE_NAME);
				items.add(uosd);
			}
		}
		
		return items;
	}
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	

	
	

	
	
	
	private void buildProfileRecipientsProviderCache() {
		for(String serviceId : listAllowedServices()) {
			BaseManager manager = WT.getServiceManager(serviceId, true, getTargetProfileId());
			if(manager instanceof IRecipientsProvidersSource) {
				List<RecipientsProviderBase> providers = ((IRecipientsProvidersSource)manager).returnRecipientsProviders();
				if(providers == null) continue;
				
				for(RecipientsProviderBase provider : providers) {
					final CompositeId cid = new CompositeId().setTokens(serviceId, provider.getId());
					cacheProfileRecipientsProvider.put(cid.toString(), provider);
				}
			}
		}
	}
	
	private LinkedHashMap<String, RecipientsProviderBase> getProfileRecipientsProviders() {
		synchronized(cacheProfileRecipientsProvider) {
			if(!cacheReady.contains("cacheProfileRecipientsProvider")) {
				buildProfileRecipientsProviderCache();
				cacheReady.add("cacheProfileRecipientsProvider");
			}
			return cacheProfileRecipientsProvider;
		}
	}
	
	
	
	/*
	public boolean writeLog(String action, String remoteIp, String userAgent, String sessionId, String data) {
		//TODO: trovare modo di completare serviceId (ora a "")
		return wta.getLogManager().write(RunContext.getProfileId(), "", action, getSoftwareName(), remoteIp, userAgent, sessionId, data);
	}
	
	public boolean writeLog(String action, String data) {
		//TODO: trovare modo di completare serviceId (ora a "")
		return wta.getLogManager().write(RunContext.getProfileId(), "", action, getSoftwareName(), null, null, null, data);
	}
	*/
	
	
	
	
	
	
	public UserProfileId userUidToProfileId(String userUid) {
		return wta.getWebTopManager().uidToUser(userUid);
	}
	
	
	
	
	
	public String getInternetUserId(UserProfileId pid) throws WTException {
		return wta.getWebTopManager().getInternetUserId(pid);
	}
	
	
	/*
	public String getUserCompleteEmailAddress(UserProfileId pid) throws Exception {
		String address = getUserEmailAddress(pid);
		String displayName = getUserDisplayName(pid);
		return new InternetAddress(address, displayName).toUnicodeString();
	}
	*/
	
	public List<UserProfileId> listProfilesWithSetting(String serviceId, String key, Object value) throws WTException {
		SettingsManager setm = wta.getSettingsManager();
		return setm.listProfilesWith(serviceId, key, value);
	}
	
	public List<Activity> listAllLiveActivities() throws WTException {
		ActivityDAO actDao = ActivityDAO.getInstance();
		ArrayList<Activity> items = new ArrayList<>();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			for(OActivity oact : actDao.selectLiveByDomain(con, getTargetProfileId().getDomainId())) {
				items.add(createActivity(oact));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<Activity> listLiveActivities() throws WTException {
		ActivityDAO actDao = ActivityDAO.getInstance();
		ArrayList<Activity> items = new ArrayList<>();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			for(OActivity oact : actDao.selectLiveByDomainUser(con, getTargetProfileId().getDomainId(), getTargetProfileId().getUserId())) {
				items.add(createActivity(oact));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public Activity getActivity(int activityId) throws WTException {
		ActivityDAO dao = ActivityDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			OActivity oact = dao.select(con, activityId);
			return createActivity(oact);
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public Activity addActivity(Activity activity) throws WTException {
		Connection con = null;
		
		try {
			ensureProfileDomain(activity.getDomainId());
			RunContext.ensureIsPermitted(false, SERVICE_ID, "ACTIVITIES", "MANAGE");
			
			con = WT.getCoreConnection();
			activity = doActivityUpdate(true, con, activity);
			writeLog("ACTIVITY_INSERT", String.valueOf(activity.getActivityId()));
			
			return activity;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public Activity updateActivity(Activity activity) throws WTException {
		Connection con = null;
		
		try {
			ensureProfileDomain(activity.getDomainId());
			RunContext.ensureIsPermitted(false, SERVICE_ID, "ACTIVITIES", "MANAGE");
			
			con = WT.getCoreConnection();
			activity = doActivityUpdate(false, con, activity);
			writeLog("ACTIVITY_UPDATE", String.valueOf(activity.getActivityId()));
			
			return activity;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public int deleteActivity(int activityId) throws WTException {
		ActivityDAO dao = ActivityDAO.getInstance();
		Connection con = null;
		
		try {
			Activity act = getActivity(activityId);
			if (act == null) return -1;
			ensureProfileDomain(act.getDomainId());
			RunContext.ensureIsPermitted(false, SERVICE_ID, "ACTIVITIES", "MANAGE");
			
			con = WT.getCoreConnection();
			int ret = dao.logicDelete(con, activityId);
			writeLog("ACTIVITY_DELETE", String.valueOf(activityId));
			return ret;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<CausalExt> listAllLiveCausals() throws WTException {
		CausalDAO dao = CausalDAO.getInstance();
		ArrayList<CausalExt> items = new ArrayList<>();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			List<VCausal> vcaus = dao.viewLiveByDomain(con, getTargetProfileId().getDomainId());
			for(VCausal vcai : vcaus) {
				items.add(createCausalExt(vcai));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<Causal> listLiveCausals(String masterDataId) throws WTException {
		CausalDAO dao = CausalDAO.getInstance();
		ArrayList<Causal> items = new ArrayList<>();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			List<OCausal> ocaus = null;
			if (!StringUtils.isBlank(masterDataId)) {
				ocaus = dao.selectLiveByDomainUserMasterData(con, getTargetProfileId().getDomainId(), getTargetProfileId().getUserId(), masterDataId);
			} else {
				ocaus = dao.selectLiveByDomainUser(con, getTargetProfileId().getDomainId(), getTargetProfileId().getUserId());
			}
			for(OCausal ocau : ocaus) {
				items.add(createCausal(ocau));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public Causal getCausal(int causalId) throws WTException {
		CausalDAO dao = CausalDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			OCausal ocal = dao.select(con, causalId);
			return createCausal(ocal);
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public Causal addCausal(Causal causal) throws WTException {
		Connection con = null;
		
		try {
			ensureProfileDomain(causal.getDomainId());
			RunContext.ensureIsPermitted(false, SERVICE_ID, "CAUSALS", "MANAGE");
			
			con = WT.getCoreConnection();
			causal = doCausalUpdate(true, con, causal);
			writeLog("CAUSAL_INSERT", String.valueOf(causal.getCausalId()));
			
			return causal;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public Causal updateCausal(Causal causal) throws WTException {
		Connection con = null;
		
		try {
			ensureProfileDomain(causal.getDomainId());
			RunContext.ensureIsPermitted(false, SERVICE_ID, "CAUSALS", "MANAGE");
			
			con = WT.getCoreConnection();
			causal = doCausalUpdate(false, con, causal);
			writeLog("CAUSAL_UPDATE", String.valueOf(causal.getCausalId()));
			
			return causal;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public int deleteCausal(int causalId) throws WTException {
		CausalDAO dao = CausalDAO.getInstance();
		Connection con = null;
		
		try {
			Causal cau = getCausal(causalId);
			if (cau == null) return -1;
			ensureProfileDomain(cau.getDomainId());
			RunContext.ensureIsPermitted(false, SERVICE_ID, "CAUSALS", "MANAGE");
			
			con = WT.getCoreConnection();
			int ret = dao.logicDelete(con, causalId);
			writeLog("CAUSAL_DELETE", String.valueOf(causalId));
			return ret;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public Map<String, MasterDataLookup> lookupMasterData(Collection<String> masterDataIds) throws WTException {
		MasterDataDAO masDao = MasterDataDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			LinkedHashMap<String, MasterDataLookup> items = new LinkedHashMap<>();
			for (OMasterData omd : masDao.viewByDomainIn(con, getTargetProfileId().getDomainId(), masterDataIds)) {
				items.put(omd.getMasterDataId(), ManagerUtils.createMasterDataLookup(omd));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public Map<String, MasterData> listMasterDataIn(Collection<String> masterDataIds) throws WTException {
		MasterDataDAO masDao = MasterDataDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			LinkedHashMap<String, MasterData> items = new LinkedHashMap<>();
			for (OMasterData omd : masDao.viewByIdsDomain(con, masterDataIds, getTargetProfileId().getDomainId())) {
				items.put(omd.getMasterDataId(), ManagerUtils.createMasterData(omd));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public Map<String, MasterData> listMasterData(Collection<String> masterDataTypes) throws WTException {
		return listMasterData(masterDataTypes, null);
	}
	
	public Map<String, MasterData> listMasterData(Collection<String> masterDataTypes, String pattern) throws WTException {
		MasterDataDAO masDao = MasterDataDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			LinkedHashMap<String, MasterData> items = new LinkedHashMap<>();
			for (OMasterData omd : masDao.viewParentsByDomainTypePattern(con, getTargetProfileId().getDomainId(), masterDataTypes, pattern)) {
				items.put(omd.getMasterDataId(), ManagerUtils.createMasterData(omd));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public Map<String, MasterData> listChildrenMasterData(Collection<String> masterDataTypes) throws WTException {
		MasterDataDAO masDao = MasterDataDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			LinkedHashMap<String, MasterData> items = new LinkedHashMap<>();
			for (OMasterData omd : masDao.viewChildrenByDomainType(con, getTargetProfileId().getDomainId(), masterDataTypes)) {
				items.put(omd.getMasterDataId(), ManagerUtils.createMasterData(omd));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<MasterData> listChildrenMasterData(String parentId, Collection<String> masterDataTypes) throws WTException {
		return listChildrenMasterData(parentId, masterDataTypes, null);
	}
	
	public List<MasterData> listChildrenMasterData(String parentId, Collection<String> masterDataTypes, String pattern) throws WTException {
		MasterDataDAO masDao = MasterDataDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			ArrayList<MasterData> items = new ArrayList<>();
			for (OMasterData omas : masDao.viewChildrenByDomainParentTypePattern(con, getTargetProfileId().getDomainId(), parentId, masterDataTypes, pattern)) {
				items.add(ManagerUtils.createMasterData(omas));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public MasterData getMasterData(String masterDataId) throws WTException {
		MasterDataDAO masDao = MasterDataDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			OMasterData omas = masDao.selectByDomainId(con, getTargetProfileId().getDomainId(), masterDataId);
			return ManagerUtils.createMasterData(omas);
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<IMChat> listIMChats(boolean skipUnavailable) throws WTException {
		IMChatDAO dao = IMChatDAO.getInstance();
		ArrayList<IMChat> items = new ArrayList<>();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			List<String> revStatuses = null;
			if (skipUnavailable) {
				revStatuses = Arrays.asList(
					EnumUtils.toSerializedName(IMChat.RevisionStatus.MODIFIED)
				);
			} else {
				revStatuses = Arrays.asList(
					EnumUtils.toSerializedName(IMChat.RevisionStatus.MODIFIED),
					EnumUtils.toSerializedName(IMChat.RevisionStatus.UNAVAILABLE)
				);
			}
			List<OIMChat> ochats = dao.selectByProfileRevStatus(con, getTargetProfileId(), revStatuses);
			for(OIMChat ocha : ochats) {
				items.add(createIMChat(ocha));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public IMChat getIMChat(String chatJid) throws WTException {
		IMChatDAO dao = IMChatDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			
			OIMChat ocha = dao.selectAliveByProfileChat(con, getTargetProfileId(), chatJid);
			return (ocha != null) ? createIMChat(ocha) : null;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public IMChat addIMChat(IMChat chat) throws WTException {
		IMChatDAO dao = IMChatDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			
			OIMChat ocha = createOIMChat(chat);
			ocha.setId(dao.getSequence(con).intValue());
			ocha.setDomainId(getTargetProfileId().getDomainId());
			ocha.setUserId(getTargetProfileId().getUserId());
			dao.insert(con, ocha, createRevisionTimestamp());	
			
			return createIMChat(ocha);
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public boolean updateIMChatLastSeenActivity(String chatJid) throws WTException {
		return updateIMChatLastSeenActivity(chatJid, createRevisionTimestamp());
	}
	
	public boolean updateIMChatLastSeenActivity(String chatJid, DateTime lastSeenActivity) throws WTException {
		IMChatDAO dao = IMChatDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			return dao.updateLastActivityByProfileChat(con, getTargetProfileId(), chatJid, lastSeenActivity, createRevisionTimestamp()) == 1;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public boolean updateIMChatAvailablity(String chatJid, boolean available) throws WTException {
		IMChatDAO dao = IMChatDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			IMChat.RevisionStatus revisionStatus = available ? IMChat.RevisionStatus.MODIFIED : IMChat.RevisionStatus.UNAVAILABLE;
			return dao.updateRevisionStatusByProfileChat(con, getTargetProfileId(), chatJid, createRevisionTimestamp(), revisionStatus) == 1;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public int deleteIMChat(String chatJid) throws WTException {
		IMChatDAO chaDao = IMChatDAO.getInstance();
		IMMessageDAO mesDao = IMMessageDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection(false);
			
			int ret = chaDao.logicDeleteByProfileChat(con, getTargetProfileId(), chatJid, createRevisionTimestamp());
			mesDao.deleteByProfileChat(con, getTargetProfileId(), chatJid);
			DbUtils.commitQuietly(con);
			return ret;
			
		} catch(SQLException | DAOException ex) {
			DbUtils.rollbackQuietly(con);
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<LocalDate> listIMMessageDates(String chatJid, int year, DateTimeZone timezone) throws WTException {
		IMMessageDAO imesDao = IMMessageDAO.getInstance();
		ArrayList<LocalDate> items = new ArrayList<>();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			List<DateTime> dts = imesDao.selectDatesByProfileChatYear(con, getTargetProfileId(), chatJid, year, timezone);
			for(DateTime dt : dts) {
				items.add(dt.withZone(timezone).toLocalDate());
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<IMMessage> listIMMessages(String chatJid, LocalDate date, DateTimeZone timezone, boolean byDelivery) throws WTException {
		IMMessageDAO imesDao = IMMessageDAO.getInstance();
		ArrayList<IMMessage> items = new ArrayList<>();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			
			List<OIMMessage> omess = imesDao.selectByProfileChatDate(con, getTargetProfileId(), chatJid, date, timezone, byDelivery);
			for(OIMMessage omes : omess) {
				items.add(createIMMessage(omes));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<IMMessage> findIMMessagesByQuery(String chatJid, String query, DateTimeZone timezone) throws WTException {
		IMMessageDAO imesDao = IMMessageDAO.getInstance();
		ArrayList<IMMessage> items = new ArrayList<>();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			
			List<OIMMessage> omess = null;
			if (query == null) {
				omess = imesDao.findByProfileChat(con, getTargetProfileId(), chatJid);
			} else {
				omess = imesDao.findByProfileChatLike(con, getTargetProfileId(), chatJid, query);
			}
			for(OIMMessage omes : omess) {
				items.add(createIMMessage(omes));
			}
			return items;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<String> listIMMessageStanzaIDs(String chatJid) throws WTException {
		IMMessageDAO imesDao = IMMessageDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			return imesDao.selectStanzaIDsByProfileChat(con, getTargetProfileId(), chatJid);
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void addIMMessage(IMMessage message) throws WTException {
		IMMessageDAO imesDao = IMMessageDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			
			OIMMessage omes = createOIMMessage(message);
			omes.setId(imesDao.getSequence(con).intValue());
			omes.setDomainId(getTargetProfileId().getDomainId());
			omes.setUserId(getTargetProfileId().getUserId());
			imesDao.insert(con, omes);	
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public OTPManager.EmailConfig otpConfigureUsingEmail(String emailAddress) throws WTException {
		//TODO: controllo accessi
		ensureUser();
		OTPManager otp = getOTPManager();
		return otp.configureEmail(getTargetProfileId(), emailAddress);
	}
	
	public OTPManager.GoogleAuthConfig otpConfigureUsingGoogleAuth(int qrCodeSize) throws WTException {
		//TODO: controllo accessi
		ensureUser();
		OTPManager otp = getOTPManager();
		return otp.configureGoogleAuth(getTargetProfileId(), qrCodeSize);
	}
	
	public boolean otpActivate(OTPManager.Config config, int code) throws WTException {
		//TODO: controllo accessi
		ensureUser();
		OTPManager otp = getOTPManager();
		return otp.activate(getTargetProfileId(), config, code);
	}
	
	public void otpDeactivate() throws WTException {
		//TODO: controllo accessi
		ensureUser();
		OTPManager otp = getOTPManager();
		otp.deactivate(getTargetProfileId());
	}
	
	public OTPManager.Config otpPrepareVerifyCode() throws WTException {
		ensureCallerService(SERVICE_ID, "otpPrepareVerifyCode");
		OTPManager otp = getOTPManager();
		return otp.prepareCheckCode(getTargetProfileId());
	}
	
	public boolean otpVerifyCode(OTPManager.Config params, int code) throws WTException {
		ensureCallerService(SERVICE_ID, "otpVerifyCode");
		OTPManager otp = getOTPManager();
		return otp.checkCode(getTargetProfileId(), params, code);
	}
	
	public List<IncomingShareRoot> listIncomingShareRoots(String serviceId, String groupName) throws WTException {
		WebTopManager wtmgr = wta.getWebTopManager();
		ShareDAO shadao = ShareDAO.getInstance();
		UserDAO usedao = UserDAO.getInstance();
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			String profileUid = wtmgr.userToUid(targetPid);
			List<String> roleUids = wtmgr.getComputedRolesAsStringByUser(targetPid, true, true);
			
			String rootKey = OShare.buildRootKey(groupName);
			String folderKey = OShare.buildFolderKey(groupName);
			String rootPermissionKey = ServiceSharePermission.buildRootPermissionKey(groupName);
			String folderPermissionKey = ServiceSharePermission.buildFolderPermissionKey(groupName);
			String elementsPermissionKey = ServiceSharePermission.buildElementsPermissionKey(groupName);
			
			con = WT.getCoreConnection();
			
			// In order to find incoming root, we need to pass through folders
			// that have at least a permission, getting incoming uids.
			// We look into permission returning each share instance that have 
			// "*@SHARE_FOLDER" as key and satisfies a set of roles. Then we can
			// get a list of unique uids (from shares table) that owns the share.
			List<String> permissionKeys = Arrays.asList(rootPermissionKey, folderPermissionKey, elementsPermissionKey);
			List<String> originUids = shadao.viewOriginByRoleServiceKey(con, roleUids, serviceId, folderKey, permissionKeys);
			ArrayList<IncomingShareRoot> roots = new ArrayList<>();
			for (String uid : originUids) {
				if (uid.equals(profileUid)) continue; // Skip self role
				
				// Foreach incoming uid we have to find the root share and then
				// test if READ right is allowed
				
				OShare root = shadao.selectByUserServiceKeyInstance(con, uid, serviceId, rootKey, OShare.INSTANCE_ROOT);
				if (root == null) continue;
				OUser user = usedao.selectByUid(con, uid);
				if (user == null) continue;
				
				roots.add(new IncomingShareRoot(root.getShareId().toString(), wtmgr.uidToUser(root.getUserUid()), user.getDisplayName()));
			}
			Collections.sort(roots, (IncomingShareRoot ish1, IncomingShareRoot ish2) -> ish1.getDescription().compareTo(ish2.getDescription()));
			return roots;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "Unable to list share roots for {0}", targetPid.toString());
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	/**
	 * Check if an SMS provider has been configured.
	 * @return true if the SMS instance has been configured, false otherwise
	 */
	
	public boolean smsConfigured() {
		initSms();
		return sms!=null;
	}
	
	public SmsProvider smsGetProvider() {
		initSms();
		return sms;
	}
	
	/**
	 * Send SMS through the configured SMS provider.
	 * @param number The destination number
	 * @param text The SMS text
	 * @return true if the SMS was accpeted by the provider, false otherwise
	 */
	
	public void smsSend(String number, String text) throws WTException {
		initSms();
		if (sms==null) {
			throw new WTException("SMS not initialized");
		}
		UserProfileId targetPid = getTargetProfileId();
		CoreServiceSettings css = new CoreServiceSettings(CoreManifest.ID, targetPid.getDomainId());	
		CoreUserSettings us = new CoreUserSettings(targetPid);
		
		//use common service settings or webtop username/password
		String username=css.getSmsWebrestUser();
		if (username==null) username=targetPid.getUserId();
		String spassword=css.getSmsWebrestPassword();
		char[] password=spassword!=null?spassword.toCharArray():RunContext.getPrincipal().getPassword();
		
		String sender=css.getSmsSender();
		String userSender=us.getSmsSender();
		if (userSender!=null && userSender.trim().length()>0) sender=userSender;
		
		if (sender==null) sender=wta.getPlatformName();
		
		boolean isAlpha=StringUtils.isAlpha(sender);
		String fromMobile=isAlpha?null:sender;
		String fromName=isAlpha?sender:null;
		sms.send(fromName, fromMobile, number, text, username, password);
	}
	
	/**
	 * Check if a PBX has been configured.
	 * @return true if the PBX instance has been configured, false otherwise
	 */
	
	public boolean pbxConfigured() {
		initPbx();
		return pbx!=null;
	}
	
	/**
	 * Get the configured PBX provider instance.
	 * @return The PBX provider instance
	 */
	
	public PbxProvider pbxGetProvider() {
		initPbx();
		return pbx;
	}
	
	/**
	 * Run PBX call through the configured PBX provider.
	 * @param number The number to call
	 * @return true if the call was accpeted by the provider, false otherwise
	 */
	
	public void pbxCall(String number) throws WTException {
		initPbx();
		if (pbx==null) {
			throw new WTException("Pbx not initialized");
		}
		UserProfileId targetPid = getTargetProfileId();
		CoreUserSettings us = new CoreUserSettings(targetPid);
		
		//use webtop username/password or from user settings
		String username=us.getPbxUsername();
		if (username==null) username=targetPid.getUserId();
		String spassword=us.getPbxPassword();
		char[] password=spassword!=null?spassword.toCharArray():RunContext.getPrincipal().getPassword();
		
		pbx.call(number, username, password);
	}
	
	/**
	 * Lists incoming share folders (level 1, eg: Calendars, Categories, etc) 
	 * for the targetProfile.
	 * @param rootShareId The root share ID
	 * @param groupName The permission groupName
	 * @return
	 * @throws WTException 
	 */
	public List<OShare> listIncomingShareFolders(String rootShareId, String groupName) throws WTException {
		ShareDAO shadao = ShareDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			
			OShare rootShare = shadao.selectById(con, Integer.valueOf(rootShareId));
			if(rootShare == null) throw new WTException("Unable to find root share [{0}]", rootShareId);
			
			String folderShareKey = OShare.buildFolderKey(groupName);
			String folderPermissionKey = ServiceSharePermission.buildFolderPermissionKey(groupName);
			
			ArrayList<OShare> folders = new ArrayList<>();
			List<OShare> shares = shadao.selectByUserServiceKey(con, rootShare.getUserUid(), rootShare.getServiceId(), folderShareKey);
			for(OShare share : shares) {
				if(RunContext.isPermitted(true, getTargetProfileId(), rootShare.getServiceId(), folderPermissionKey, ServicePermission.ACTION_READ, share.getShareId().toString())) {
					folders.add(share);
				}
			}
			return folders;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public <T>T getIncomingShareFolderData(String shareId, Class<T> type) throws WTException {
		WebTopManager usrm = wta.getWebTopManager();
		ShareDAO shadao = ShareDAO.getInstance();
		ShareDataDAO shddao = ShareDataDAO.getInstance();
		Connection con = null;
		
		try {
			String profileUid = usrm.userToUid(getTargetProfileId());
			con = WT.getCoreConnection();
			
			OShare share = shadao.selectById(con, Integer.valueOf(shareId));
			if(share == null) throw new WTException("Unable to find share [{0}]", shareId);
			if(!areActionsPermittedOnShare(share, ServiceSharePermission.TARGET_FOLDER, new String[]{ServicePermission.ACTION_READ})[0]) {
				throw new WTException("Share not accessible [{0}]", shareId);
			}
			
			OShareData data = shddao.selectByShareUser(con, Integer.valueOf(shareId), profileUid);
			if(data != null) {
				return LangUtils.value(data.getValue(), null, type);
			} else {
				return null;
			}
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public boolean[] areActionsPermittedOnShare(String shareId, String permissionTarget, String[] actions) throws WTException {
		ShareDAO shadao = ShareDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			
			OShare share = shadao.selectById(con, Integer.valueOf(shareId));
			if(share == null) throw new WTException("Unable to find share [{0}]", shareId);
			return areActionsPermittedOnShare(share, permissionTarget, actions);
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public boolean[] areActionsPermittedOnShare(OShare share, String permissionTarget, String[] actions) throws WTException {
		String instance = String.valueOf(share.getShareId());
		String groupName = OShare.extractGroupNameFromKey(share.getKey());
		String permKey = ServiceSharePermission.buildPermissionKey(permissionTarget, groupName);
		
		UserProfileId targetPid = getTargetProfileId();
		boolean[] perms = new boolean[actions.length];
		for(int i=0; i<actions.length; i++) {
			perms[i] = RunContext.isPermitted(true, targetPid, share.getServiceId(), permKey, actions[i], instance);
		}
		return perms;
	}
	
	public boolean isShareRootPermitted(String shareId, String action) throws WTException {
		return areActionsPermittedOnShare(shareId, ServiceSharePermission.TARGET_ROOT, new String[]{action})[0];
	}
	
	public boolean isShareFolderPermitted(String shareId, String action) throws WTException {
		return areActionsPermittedOnShare(shareId, ServiceSharePermission.TARGET_FOLDER, new String[]{action})[0];
	}
	
	public boolean isShareElementsPermitted(String shareId, String action) throws WTException {
		return areActionsPermittedOnShare(shareId, ServiceSharePermission.TARGET_ELEMENTS, new String[]{action})[0];
	}
	
	public SharePermsRoot getShareRootPermissions(String shareId) throws WTException {
		boolean[] bools = areActionsPermittedOnShare(shareId, ServiceSharePermission.TARGET_ROOT, SharePermsRoot.ACTIONS);
		return new SharePermsRoot(SharePermsRoot.ACTIONS, bools);
	}
	
	public SharePermsFolder getShareFolderPermissions(String shareId) throws WTException {
		boolean[] bools = areActionsPermittedOnShare(shareId, ServiceSharePermission.TARGET_FOLDER, SharePermsFolder.ACTIONS);
		return new SharePermsFolder(SharePermsFolder.ACTIONS, bools);
	}
	
	public SharePermsElements getShareElementsPermissions(String shareId) throws WTException {
		boolean[] bools = areActionsPermittedOnShare(shareId, ServiceSharePermission.TARGET_ELEMENTS, SharePermsElements.ACTIONS);
		return new SharePermsElements(SharePermsElements.ACTIONS, bools);
	}
	
	public Sharing getSharing(String serviceId, String groupName, String shareId) throws WTException {
		WebTopManager usrm = wta.getWebTopManager();
		ShareDAO shadao = ShareDAO.getInstance();
		RolePermissionDAO rpedao = RolePermissionDAO.getInstance();
		Connection con = null;
		
		String rootShareKey = OShare.buildRootKey(groupName);
		String folderShareKey = OShare.buildFolderKey(groupName);
		String rootPermissionKey = ServiceSharePermission.buildRootPermissionKey(groupName);
		String folderPermissionKey = ServiceSharePermission.buildFolderPermissionKey(groupName);
		String elementsPermissionKey = ServiceSharePermission.buildElementsPermissionKey(groupName);
		
		try {
			CompositeId cid = new CompositeId().parse(shareId);
			int level = cid.getSize()-1;
			String rootId = cid.getToken(0);
			
			con = WT.getCoreConnection();
			
			// Retrieves the root share
			OShare rootShare = null;
			if(rootId.equals("0")) {
				String puid = usrm.userToUid(getTargetProfileId());
				rootShare = shadao.selectByUserServiceKeyInstance(con, puid, serviceId, rootShareKey, OShare.INSTANCE_ROOT);
			} else {
				rootShare = shadao.selectById(con, Integer.valueOf(rootId));
			}
			
			Sharing outshare = new Sharing();
			outshare.setId(shareId);
			outshare.setLevel(level);
			
			if(rootShare != null) { // A rootShare must be defined in order to continue...
				if(level == 0) {
					LinkedHashSet<String> roleUids = new LinkedHashSet<>();
					roleUids.addAll(listRoles(serviceId, rootPermissionKey, rootShare.getShareId().toString()));
					
					OShare folderShare = shadao.selectByUserServiceKeyInstance(con, rootShare.getUserUid(), serviceId, folderShareKey, OShare.INSTANCE_WILDCARD);
					if(folderShare != null) roleUids.addAll(listRoles(serviceId, folderPermissionKey, folderShare.getShareId().toString()));

					for(String roleUid : roleUids) {
						// Root...
						SharePermsRoot rperms = new SharePermsRoot();
						for(ORolePermission perm : rpedao.selectByRoleServiceKeyInstance(con, roleUid, serviceId, rootPermissionKey, rootShare.getShareId().toString())) {
							rperms.parse(perm.getAction());
						}
						// Folder...
						SharePermsFolder fperms = new SharePermsFolder();
						if(folderShare != null) {
							for(ORolePermission perm : rpedao.selectByRoleServiceKeyInstance(con, roleUid, serviceId, folderPermissionKey, folderShare.getShareId().toString())) {
								fperms.parse(perm.getAction());
							}
						}
							
						// Elements...
						SharePermsElements eperms = new SharePermsElements();
						if(folderShare != null) {
							for(ORolePermission perm : rpedao.selectByRoleServiceKeyInstance(con, roleUid, serviceId, elementsPermissionKey, folderShare.getShareId().toString())) {
								eperms.parse(perm.getAction());
							}
						}
						outshare.getRights().add(new Sharing.RoleRights(roleUid, rperms, fperms, eperms));
					}


				} else if(level == 1) {
					String folderId = cid.getToken(1);
					OShare folderShare = shadao.selectByUserServiceKeyInstance(con, rootShare.getUserUid(), serviceId, folderShareKey, folderId);

					if(folderShare != null) {
						List<String> roleUids = listRoles(serviceId, folderPermissionKey, folderShare.getShareId().toString());
						for(String roleUid : roleUids) {
							// Folder...
							SharePermsFolder fperms = new SharePermsFolder();
							for(ORolePermission perm : rpedao.selectByRoleServiceKeyInstance(con, roleUid, serviceId, folderPermissionKey, folderShare.getShareId().toString())) {
								fperms.parse(perm.getAction());
							}
							// Elements...
							SharePermsElements eperms = new SharePermsElements();
							for(ORolePermission perm : rpedao.selectByRoleServiceKeyInstance(con, roleUid, serviceId, elementsPermissionKey, folderShare.getShareId().toString())) {
								eperms.parse(perm.getAction());
							}
							outshare.getRights().add(new Sharing.RoleRights(roleUid, null, fperms, eperms));
						}
					}
				}
			}
			
			return outshare;
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void updateSharing(String serviceId, String groupName, Sharing sharing) throws WTException {
		WebTopManager usrm = wta.getWebTopManager();
		ShareDAO shadao = ShareDAO.getInstance();
		Connection con = null;
		
		// Sharing is handled at two levels: root(0) and folder(1).
		// The first level tracks permissions associated to any instances 
		// of items in the groupName; the second tracks permissions related to
		// a specific item instance instead.
		// If for example we are talking about addressbook contacts we can 
		// define the contact category as a groupName.
		// Of course we can have many categories: Work, Home, Prospect, etc.
		// So, looking at sharing, the root level register permissions valid 
		// for any items of the groupName (Work, Home, ...).
		// At next level, we have permissions for only the Work category.
		
		try {
			String puid = usrm.userToUid(getTargetProfileId());
			
			// Parses the sharing ID as a composite key:
			// - "0"		for root share
			// - "0|{id}"	for folder share
			CompositeId cid = new CompositeId().parse(sharing.getId());
			int level = cid.getSize()-1;
			String rootId = cid.getToken(0);
			
			String rootKey = OShare.buildRootKey(groupName);
			String folderKey = OShare.buildFolderKey(groupName);
			String rootPermRes = ServiceSharePermission.buildRootPermissionKey(groupName);
			String folderPermRes = ServiceSharePermission.buildFolderPermissionKey(groupName);
			String elementsPermRes = ServiceSharePermission.buildElementsPermissionKey(groupName);
			
			con = WT.getCoreConnection();
			
			// Retrieves the root share
			OShare rootShare = null;
			if(rootId.equals("0")) {
				rootShare = shadao.selectByUserServiceKeyInstance(con, puid, serviceId, rootKey, OShare.INSTANCE_ROOT);
			} else {
				rootShare = shadao.selectById(con, Integer.valueOf(rootId));
			}
			if(rootShare == null) rootShare = addRootShare(con, puid, serviceId, rootKey);
			
			if(level == 0) {
				OShare folderShare = shadao.selectByUserServiceKeyInstance(con, rootShare.getUserUid(), serviceId, folderKey, OShare.INSTANCE_WILDCARD);
				
				if(!sharing.getRights().isEmpty()) {
					removeRootSharePermissions(con, rootShare.getShareId().toString(), serviceId, groupName);
					if(folderShare == null) {
						folderShare = addFolderShare(con, rootShare.getUserUid(), serviceId, folderKey, OShare.INSTANCE_WILDCARD);
					} else { // Folder isn't new (and we have some rights)...
						// Removes all rights belonging to this folder share
						removeFolderSharePermissions(con, folderShare.getShareId().toString(), serviceId, groupName);
					}
					
					// Adds permissions according to specified rights...
					for(Sharing.RoleRights rr : sharing.getRights()) {
						if(rr.rootManage) addSharePermission(con, rr.roleUid, serviceId, rootPermRes, "MANAGE", rootShare.getShareId().toString());
						if(rr.folderRead) addSharePermission(con, rr.roleUid, serviceId, folderPermRes, "READ", folderShare.getShareId().toString());
						if(rr.folderUpdate) addSharePermission(con, rr.roleUid, serviceId, folderPermRes, "UPDATE", folderShare.getShareId().toString());
						if(rr.folderDelete) addSharePermission(con, rr.roleUid, serviceId, folderPermRes, "DELETE", folderShare.getShareId().toString());
						if(rr.elementsCreate) addSharePermission(con, rr.roleUid, serviceId, elementsPermRes, "CREATE", folderShare.getShareId().toString());
						if(rr.elementsUpdate) addSharePermission(con, rr.roleUid, serviceId, elementsPermRes, "UPDATE", folderShare.getShareId().toString());
						if(rr.elementsDelete) addSharePermission(con, rr.roleUid, serviceId, elementsPermRes, "DELETE", folderShare.getShareId().toString());
					}
					
				} else {
					// If defines, removes folder share and its rights
					if(folderShare != null) removeFolderShare(con, folderShare.getShareId().toString(), serviceId, groupName);
				}
				
			} else if(level == 1) {
				String folderId = cid.getToken(1);
				OShare folderShare = shadao.selectByUserServiceKeyInstance(con, rootShare.getUserUid(), serviceId, folderKey, folderId);
				
				if(!sharing.getRights().isEmpty()) {
					if(folderShare == null) {
						folderShare = addFolderShare(con, rootShare.getUserUid(), serviceId, folderKey, folderId);
					} else { // Folder isn't new (and we have some rights)...
						// Removes all rights belonging to this folder share
						removeFolderSharePermissions(con, folderShare.getShareId().toString(), serviceId, groupName);
					}

					// Adds permissions according to specified rights...
					for(Sharing.RoleRights rr : sharing.getRights()) {
						//if(rr.rootManage) addSharePermission(con, rr.roleUid, serviceId, rootPermRes, "MANAGE", rootShare.getShareId().toString());
						if(rr.folderRead) addSharePermission(con, rr.roleUid, serviceId, folderPermRes, "READ", folderShare.getShareId().toString());
						if(rr.folderUpdate) addSharePermission(con, rr.roleUid, serviceId, folderPermRes, "UPDATE", folderShare.getShareId().toString());
						if(rr.folderDelete) addSharePermission(con, rr.roleUid, serviceId, folderPermRes, "DELETE", folderShare.getShareId().toString());
						if(rr.elementsCreate) addSharePermission(con, rr.roleUid, serviceId, elementsPermRes, "CREATE", folderShare.getShareId().toString());
						if(rr.elementsUpdate) addSharePermission(con, rr.roleUid, serviceId, elementsPermRes, "UPDATE", folderShare.getShareId().toString());
						if(rr.elementsDelete) addSharePermission(con, rr.roleUid, serviceId, elementsPermRes, "DELETE", folderShare.getShareId().toString());
					}

				} else { // No rights specified for any role...
					// If defines, removes folder share and its rights
					if(folderShare != null) removeFolderShare(con, folderShare.getShareId().toString(), serviceId, groupName);
				}
			}
			
			DbUtils.commitQuietly(con);
			
		} catch(SQLException | DAOException ex) {
			DbUtils.rollbackQuietly(con);
			throw new WTException(ex, "DB error");
		} catch(Exception ex) {
			DbUtils.rollbackQuietly(con);
			throw new WTException(ex, "Unable to update share rights");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	private ORolePermission addSharePermission(Connection con, String roleUid, String service, String key, String action, String instance) throws DAOException {
		RolePermissionDAO rpedao = RolePermissionDAO.getInstance();
		ORolePermission rp = new ORolePermission();
		rp.setRoleUid(roleUid);
		rp.setServiceId(service);
		rp.setKey(key);
		rp.setAction(action);
		rp.setInstance(instance);
		rp.setRolePermissionId(rpedao.getSequence(con).intValue());
		rpedao.insert(con, rp);
		return rp;
	}
	
	private void removeRootSharePermissions(Connection con, String shareId, String serviceId, String groupName) throws DAOException {
		String rootPermissionKey = ServiceSharePermission.buildRootPermissionKey(groupName);
		RolePermissionDAO rpedao = RolePermissionDAO.getInstance();
		rpedao.deleteByServiceKeyInstance(con, serviceId, rootPermissionKey, shareId);
	}
	
	private void removeFolderShare(Connection con, String shareId, String serviceId, String groupName) throws DAOException {
		ShareDAO shadao = ShareDAO.getInstance();
		
		// 1 - Deletes main folder share record
		shadao.deleteById(con, Integer.valueOf(shareId));
		
		// 2 - Deletes any permission related to folder share
		removeFolderSharePermissions(con, shareId, serviceId, groupName);
	}
	
	private void removeFolderSharePermissions(Connection con, String shareId, String serviceId, String groupName) throws DAOException {
		String folderPermissionKey = ServiceSharePermission.buildFolderPermissionKey(groupName);
		String elementsPermissionKey = ServiceSharePermission.buildElementsPermissionKey(groupName);
		RolePermissionDAO rpedao = RolePermissionDAO.getInstance();
		rpedao.deleteByServiceKeyInstance(con, serviceId, folderPermissionKey, shareId);
		rpedao.deleteByServiceKeyInstance(con, serviceId, elementsPermissionKey, shareId);
	}
	
	private OShare addRootShare(Connection con, String userUid, String serviceId, String shareKey) throws DAOException {
		ShareDAO dao = ShareDAO.getInstance();
		OShare share = new OShare();
		share.setUserUid(userUid);
		share.setServiceId(serviceId);
		share.setKey(shareKey);
		share.setInstance(OShare.INSTANCE_ROOT);
		share.setShareId(dao.getSequence(con).intValue());
		dao.insert(con, share);
		return share;
	}
	
	private OShare addFolderShare(Connection con, String userUid, String serviceId, String shareKey, String instance) throws DAOException {
		ShareDAO dao = ShareDAO.getInstance();
		OShare share = new OShare();
		share.setUserUid(userUid);
		share.setServiceId(serviceId);
		share.setKey(shareKey);
		share.setInstance(instance);
		share.setShareId(dao.getSequence(con).intValue());
		dao.insert(con, share);
		return share;
	}
			
	public List<OShare> listShareByOwner(UserProfileId pid, String serviceId, String shareKey) throws WTException {
		WebTopManager usrm = wta.getWebTopManager();
		ShareDAO dao = ShareDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			String uuid = usrm.userToUid(pid);
			return dao.selectByUserServiceKey(con, uuid, serviceId, shareKey);
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<String> listRoles(String serviceId, String permissionKey, String instance) throws WTException {
		RolePermissionDAO dao = RolePermissionDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			return dao.selectRolesByServiceKeyInstance(con, serviceId, permissionKey, instance);
			
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public OSnoozedReminder snoozeReminder(ReminderInApp reminder, DateTime remindOn) throws WTException {
		SnoozedReminderDAO dao = SnoozedReminderDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			
			OSnoozedReminder item = new OSnoozedReminder();
			item.setDomainId(reminder.getProfileId().getDomain());
			item.setUserId(reminder.getProfileId().getUserId());
			item.setServiceId(reminder.getServiceId());
			item.setType(reminder.getType());
			item.setInstanceId(reminder.getInstanceId());
			item.setRemindOn(remindOn);
			item.setTitle(reminder.getTitle());
			item.setDate(reminder.getDate());
			item.setTimezone(reminder.getTimezone());
			
			item.setSnoozedReminderId(dao.getSequence(con).intValue());
			dao.insert(con, item);
			return item;
		
		} catch(SQLException | DAOException ex) {
			throw new WTException(ex, "DB Error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<OSnoozedReminder> listExpiredSnoozedReminders(DateTime greaterInstant) throws WTException {
		SnoozedReminderDAO dao = SnoozedReminderDAO.getInstance();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			con.setAutoCommit(false);
			List<OSnoozedReminder> items = dao.selectExpiredForUpdateByInstant(con, greaterInstant);
			for(OSnoozedReminder item : items) {
				dao.delete(con, item.getSnoozedReminderId());
			}
			DbUtils.commitQuietly(con);
			return items;
			
		} catch(SQLException | DAOException ex) {
			DbUtils.rollbackQuietly(con);
			throw new WTException(ex, "DB Error");
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<OServiceStoreEntry> listServiceStoreEntriesByQuery(String serviceId, String context, String query, int max) {
		ServiceStoreEntryDAO sseDao = ServiceStoreEntryDAO.getInstance();
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			if (StringUtils.isBlank(query)) {
				return sseDao.selectKeyValueByLimit(con, targetPid.getDomainId(), targetPid.getUserId(), serviceId, context, max);
			} else {
				String newQuery = StringUtils.upperCase(StringUtils.trim(query));
				return sseDao.selectKeyValueByLikeKeyLimit(con, targetPid.getDomainId(), targetPid.getUserId(), serviceId, context, "%"+newQuery+"%", max);
			}
		
		} catch(SQLException | DAOException ex) {
			logger.error("Error querying servicestore entry [{}, {}, {}, {}]", targetPid, serviceId, context, query, ex);
			return new ArrayList<>();
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void addServiceStoreEntry(String serviceId, String context, String key, String value) {
		ServiceStoreEntryDAO sseDao = ServiceStoreEntryDAO.getInstance();
		UserProfileId targetPid = getTargetProfileId();
		OServiceStoreEntry osse = null;
		Connection con = null;
		
		try {
			if (StringUtils.isBlank(value)) return;
			con = WT.getCoreConnection();
			osse = sseDao.select(con, targetPid.getDomainId(), targetPid.getUserId(), serviceId, context, key);
			if (osse != null) {
				sseDao.update(con, osse.getDomainId(), osse.getUserId(), osse.getServiceId(), osse.getContext(), key, value);
			} else {
				osse = new OServiceStoreEntry();
				osse.setDomainId(targetPid.getDomainId());
				osse.setUserId(targetPid.getUserId());
				osse.setServiceId(serviceId);
				osse.setContext(context);
				osse.setKey(key);
				osse.setValue(value);
				osse.setFrequency(1);
				sseDao.insert(con, osse);
			}
		} catch(SQLException | DAOException ex) {
			logger.error("Error adding servicestore entry [{}, {}, {}, {}]", targetPid, serviceId, context, OServiceStoreEntry.sanitizeKey(key), ex);
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void deleteServiceStoreEntry() {
		ServiceStoreEntryDAO sseDao = ServiceStoreEntryDAO.getInstance();
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			sseDao.deleteByDomainUser(con, targetPid.getDomainId(), targetPid.getUserId());
			
		} catch(SQLException | DAOException ex) {
			logger.error("Error deleting servicestore entry [{}]", targetPid, ex);
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void deleteServiceStoreEntry(String serviceId) {
		ServiceStoreEntryDAO sseDao = ServiceStoreEntryDAO.getInstance();
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			sseDao.deleteByDomainUserService(con, targetPid.getDomainId(), targetPid.getUserId(), serviceId);
			
		} catch(SQLException | DAOException ex) {
			logger.error("Error deleting servicestore entry [{}, {}]", targetPid, serviceId, ex);
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void deleteServiceStoreEntry(String serviceId, String context, String key) {
		ServiceStoreEntryDAO sseDao = ServiceStoreEntryDAO.getInstance();
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			sseDao.delete(con, targetPid.getDomainId(), targetPid.getUserId(), serviceId, context, key);
			
		} catch(SQLException | DAOException ex) {
			logger.error("Error deleting servicestore entry [{}, {}, {}, {}]", targetPid, serviceId, context, key, ex);
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void updateMyAutosaveData(String webtopClientId, String serviceId, String context, String key, String value) {
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			AutosaveDAO asdao = AutosaveDAO.getInstance();
			OAutosave data = asdao.select(con, targetPid.getDomainId(), targetPid.getUserId(), webtopClientId, serviceId, context, key);
			
			if(data != null) {
				data.setValue(value);
				asdao.update(con, data);
			} else {
				data = new OAutosave();
				data.setDomainId(targetPid.getDomainId());
				data.setUserId(targetPid.getUserId());
				data.setWebtopClientId(webtopClientId);
				data.setServiceId(serviceId);
				data.setContext(context);
				data.setKey(StringUtils.upperCase(key));
				data.setValue(value);
				asdao.insert(con, data);
			}
			
		} catch(SQLException | DAOException ex) {
			logger.error("Error adding autosave entry [{}, {}, {}, {}]", targetPid, serviceId, context, key, ex);
			
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void deleteOfflineOthersAutosaveData(String notWebtopClientId) {
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			AutosaveDAO asdao = AutosaveDAO.getInstance();
			List<OAutosave> items=listOfflineOthersAutosaveData(notWebtopClientId);
			for(OAutosave item: items) {
				asdao.deleteByKey(con, targetPid.getDomainId(), targetPid.getUserId(),item.getWebtopClientId(),item.getServiceId(),item.getContext(),item.getKey());
			}
		} catch(SQLException | DAOException ex) {
			logger.error("Error deleting autosave entry [{}, !{}]", targetPid, notWebtopClientId, ex);
			
		} finally {
			DbUtils.closeQuietly(con);
		}

	}
	
	public void deleteMyAutosaveData(String webtopClientId) {
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			AutosaveDAO asdao = AutosaveDAO.getInstance();
			asdao.deleteByWebtopClientId(con, targetPid.getDomainId(), targetPid.getUserId(), webtopClientId);
		} catch(SQLException | DAOException ex) {
			logger.error("Error deleting autosave entry [{}, {}]", targetPid, webtopClientId, ex);
			
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void deleteMyAutosaveData(String webtopClientId, String serviceId) {
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			AutosaveDAO asdao = AutosaveDAO.getInstance();
			asdao.deleteByService(con, targetPid.getDomainId(), targetPid.getUserId(), webtopClientId, serviceId);
		} catch(SQLException | DAOException ex) {
			logger.error("Error deleting autosave entry [{}, {}, {}]", targetPid, webtopClientId, serviceId, ex);
			
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void deleteMyAutosaveData(String webtopClientId, String serviceId, String context) {
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			AutosaveDAO asdao = AutosaveDAO.getInstance();
			asdao.deleteByContext(con, targetPid.getDomainId(), targetPid.getUserId(), webtopClientId, serviceId, context);
		} catch(SQLException | DAOException ex) {
			logger.error("Error deleting autosave entry [{}, {}, {}, {}]", targetPid, webtopClientId, serviceId, context, ex);
			
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void deleteMyAutosaveData(String webtopClientId, String serviceId, String context, String key) {
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			AutosaveDAO asdao = AutosaveDAO.getInstance();
			asdao.deleteByKey(con, targetPid.getDomainId(), targetPid.getUserId(), webtopClientId, serviceId, context, key);
		} catch(SQLException | DAOException ex) {
			logger.error("Error deleting autosave entry [{}, {}, {}, {}, {}]", targetPid, webtopClientId, serviceId, context, key, ex);
			
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<OAutosave> listMyAutosaveData(String webtopClientId) {
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			AutosaveDAO asdao = AutosaveDAO.getInstance();
			return asdao.selectMineByUserServices(con, targetPid.getDomainId(), targetPid.getUserId(), webtopClientId, listAllowedServices());
		} catch(SQLException | DAOException ex) {
			logger.error("Error selecting autosave entry [{}]", targetPid, ex);
			return new ArrayList<>();
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public boolean hasMyAutosaveData(String webtopClientId) {
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			con = WT.getCoreConnection();
			AutosaveDAO asdao = AutosaveDAO.getInstance();
			return asdao.countMineByUserServices(con, targetPid.getDomainId(), targetPid.getUserId(), webtopClientId, listAllowedServices()) > 0;
		} catch(SQLException | DAOException ex) {
			logger.error("Error selecting autosave entry [{}]", targetPid, ex);
			return false;
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public List<OAutosave> listOfflineOthersAutosaveData(String notWebtopClientId) {
		UserProfileId targetPid = getTargetProfileId();
		Connection con = null;
		
		try {
			SessionManager sm=wta.getSessionManager();
			//wta.getSessionManager().isOnline(targetPid, notWebtopClientId);
			con = WT.getCoreConnection();
			AutosaveDAO asdao = AutosaveDAO.getInstance();
			List<OAutosave> data=asdao.selectOthersByUserServices(con, targetPid.getDomainId(), targetPid.getUserId(), notWebtopClientId, listAllowedServices());
			List<OAutosave> rdata=new ArrayList<>();
			for(OAutosave as: data) {
				if (!sm.isOnline(new UserProfileId(as.getDomainId(),as.getUserId()), as.getWebtopClientId()))
					rdata.add(as);
			}
			return rdata;
		} catch(SQLException | DAOException ex) {
			logger.error("Error selecting autosave entry [{}]", targetPid, ex);
			return null;
		} finally {
			DbUtils.closeQuietly(con);
		}
	}
	
	public void autoLearnInternetRecipient(String email) {
		addServiceStoreEntry(SERVICE_ID, "recipients", email, email);
	}
	
	public RecipientsProviderBase getProfileRecipientsProvider(String sourceId) {
		return getProfileRecipientsProviders().get(sourceId);
	}
	
	/**
	 * Returns the available source IDs.
	 * @return
	 * @throws WTException 
	 */
	public List<String> listRecipientProviderSourceIds() throws WTException {
		return new ArrayList<>(getProfileRecipientsProviders().keySet());
	}
	
	/**
	 * Returns a list of recipients beloging to a specified type.
	 * The search will include all available sources; including also the 
	 * automatic ({@link #RECIPIENT_PROVIDER_AUTO_SOURCE_ID}) one used to store
	 * the auto-learn texts, and the ({@link #RECIPIENT_PROVIDER_WEBTOP_SOURCE_ID})
	 * one containing internal webtop users
	 * @param fieldType The desired recipient type.
	 * @param queryText A text to filter out returned results.
	 * @param max Max number of results.
	 * @param builtInProvidersAtTheEnd True add built-in providers (AUTO and WEBTOP) results at the end, false otherwise.
	 * @return
	 * @throws WTException 
	 */
	public List<Recipient> listProviderRecipients(RecipientFieldType fieldType, String queryText, int max, boolean builtInProvidersAtTheEnd) throws WTException {
		final ArrayList<String> ids = new ArrayList<>();
		if (!builtInProvidersAtTheEnd) {
			ids.add(RECIPIENT_PROVIDER_AUTO_SOURCE_ID);
			ids.add(RECIPIENT_PROVIDER_WEBTOP_SOURCE_ID);
		}
		ids.addAll(listRecipientProviderSourceIds());
		if (builtInProvidersAtTheEnd) {
			ids.add(RECIPIENT_PROVIDER_AUTO_SOURCE_ID);
			ids.add(RECIPIENT_PROVIDER_WEBTOP_SOURCE_ID);
		}
		return listProviderRecipients(fieldType, ids, queryText, max);
	}
	
	/**
	 * Returns a list of recipients beloging to a specified type.
	 * @param fieldType The desired recipient type.
	 * @param sourceIds A collection of sources in which look for.
	 * @param queryText A text to filter out returned results.
	 * @param max Max number of results.
	 * @return
	 * @throws WTException 
	 */
	public List<Recipient> listProviderRecipients(RecipientFieldType fieldType, Collection<String> sourceIds, String queryText, int max) throws WTException {
		CoreServiceSettings css = new CoreServiceSettings(CoreManifest.ID, getTargetProfileId().getDomainId());
		ArrayList<Recipient> items = new ArrayList<>();
		boolean autoProviderEnabled = css.getRecipientAutoProviderEnabled();
		
		int remaining = max;
		for (String soId : sourceIds) {
			List<Recipient> recipients = null;
			if (StringUtils.equals(soId, RECIPIENT_PROVIDER_AUTO_SOURCE_ID)) {
				if (!autoProviderEnabled) continue;
				if (!fieldType.equals(RecipientFieldType.LIST)) {
					recipients = new ArrayList<>();
					//TODO: Find a way to handle other RecipientFieldTypes
					if (fieldType.equals(RecipientFieldType.EMAIL)) {
						final List<OServiceStoreEntry> entries = listServiceStoreEntriesByQuery(SERVICE_ID, "recipients", queryText, remaining);
						for(OServiceStoreEntry entry: entries) {
							final InternetAddress ia = InternetAddressUtils.toInternetAddress(entry.getValue());
							if (ia!=null) recipients.add(new Recipient(RECIPIENT_PROVIDER_AUTO_SOURCE_ID, lookupResource(getLocale(), CoreLocaleKey.INTERNETRECIPIENT_AUTO), RECIPIENT_PROVIDER_AUTO_SOURCE_ID, ia.getPersonal(), ia.getAddress()));
						}
					}
				}
			} else if (StringUtils.equals(soId, RECIPIENT_PROVIDER_WEBTOP_SOURCE_ID)) {
				if (!fieldType.equals(RecipientFieldType.LIST)) {
					recipients = new ArrayList<>();
					//TODO: Find a way to handle other RecipientFieldTypes
					if (fieldType.equals(RecipientFieldType.EMAIL)) {
						List<OUser> users=listUsers(true);
						for(OUser user: users) {
							UserProfile.Data userData=WT.getUserData(new UserProfileId(user.getDomainId(),user.getUserId()));
							if (userData!=null) {
								if (StringUtils.containsIgnoreCase(user.getDisplayName(),queryText) || StringUtils.containsIgnoreCase(userData.getPersonalEmailAddress(),queryText))
									recipients.add(
										new Recipient(
												RECIPIENT_PROVIDER_WEBTOP_SOURCE_ID, 
												lookupResource(getLocale(), CoreLocaleKey.INTERNETRECIPIENT_WEBTOP), 
												RECIPIENT_PROVIDER_AUTO_SOURCE_ID, 
												user.getDisplayName(), 
												userData.getPersonalEmailAddress()
										)
									);
							}
						}
					}
				}
			} else {
				final RecipientsProviderBase provider = getProfileRecipientsProviders().get(soId);
				if (provider == null) continue;
				
				try {
					recipients = provider.getRecipients(fieldType, queryText, remaining);
				} catch(Throwable t) {
					logger.error("Error calling RecipientProvider [{}]", t, soId);
				}
				if (recipients == null) continue;
			}
			
			if (recipients!=null)
				for(Recipient recipient : recipients) {
					remaining--;
					if (remaining < 0) break; 
					recipient.setSource(soId); // Force composed id!
					items.add(recipient);
				}
			if (remaining <= 0) break;
		}
		return items;
	}
	
	/**
	 * Expands a virtualRecipient address into a real set of recipients.
	 * @param virtualRecipientAddress
	 * @return
	 * @throws WTException 
	 */
	public List<Recipient> expandVirtualProviderRecipient(String virtualRecipientAddress) throws WTException {
		ArrayList<Recipient> items = new ArrayList<>();
		VirtualAddress va = new VirtualAddress(virtualRecipientAddress);
		
		for (String soId : listRecipientProviderSourceIds()) {
			final RecipientsProviderBase provider = getProfileRecipientsProviders().get(soId);
			if (provider == null) continue;
			if (!StringUtils.isBlank(va.getDomain()) && !StringUtils.startsWith(soId, va.getDomain())) {
				continue;
			}
			
			List<Recipient> recipients = null;
			try {
				recipients = provider.expandToRecipients(va.getLocal());
			} catch(Throwable t) {
				logger.error("Error calling RecipientProvider [{}]", t, soId);
			}
			if (recipients == null) continue;
			for (Recipient recipient : recipients) {
				recipient.setSource(soId);
				items.add(recipient);
			}
		}
		return items;
	}
	
	public List<SyncDevice> listZPushDevices() throws WTException {
		try {
			ZPushManager zpush = createZPushManager();
			
			boolean noFilter = false, domainMatch = false;
			String match = null;
			UserProfileId targetPid = getTargetProfileId();
			if (RunContext.isSysAdmin()) {
				if (UserProfileId.isWildcardUser(targetPid)) {
					domainMatch = true;
					match = "@" + wta.getWebTopManager().getDomainInternetName(targetPid.getDomainId());
				} else {
					match = getInternetUserId(targetPid);
				}
			} else {
				match = getInternetUserId(targetPid);
			}
			
			ArrayList<SyncDevice> devices = new ArrayList<>();
			List<ZPushManager.LastsyncRecord> recs = zpush.listDevices();
			for (ZPushManager.LastsyncRecord rec : recs) {
				if (noFilter || StringUtils.equalsIgnoreCase(rec.synchronizedUser, match) || (domainMatch && StringUtils.endsWithIgnoreCase(rec.synchronizedUser, match))) {
					devices.add(new SyncDevice(rec.device, rec.synchronizedUser, rec.lastSyncTime));
				}
			}
			
			return devices;
			
		} catch(Exception ex) {
			logger.error("Error listing zpush devices",ex);
			throw new WTException(ex);
		}
	}
	
	public void deleteZPushDevice(String deviceId) throws WTException {
		UserProfileId targetPid = getTargetProfileId();
		try {
			ZPushManager zpush = createZPushManager();
			if (RunContext.isSysAdmin()) {
				if (UserProfileId.isWildcardUser(targetPid)) {
					zpush.removeDevice(deviceId);
				} else {
					zpush.removeUserDevice(getInternetUserId(targetPid), deviceId);
				}
			} else {
				zpush.removeUserDevice(getInternetUserId(targetPid), deviceId);
			}
			
		} catch(Exception ex) {
			throw new WTException(ex);
		}
	}
	
	public String getZPushDetailedInfo(String deviceId, String lineSep) throws WTException {
		UserProfileId targetPid = getTargetProfileId();
		try {
			ZPushManager zpush = createZPushManager();
			ensureProfile(true);
			return zpush.getDetailedInfo(deviceId, getInternetUserId(targetPid), lineSep);
			
		} catch(Exception ex) {
			throw new WTException(ex);
		}	
	}
	
	private ZPushManager createZPushManager() throws WTException {
		CoreServiceSettings css = new CoreServiceSettings(CoreManifest.ID, "*");
		try {
			URI uri = new URI(css.getDevicesSyncShellUri());
			return new ZPushManager(css.getPhpPath(), css.getZPushPath(), uri);
		} catch(URISyntaxException ex) {
			throw new WTException(ex, "Invalid URI");
		}
	}
	
	private Activity doActivityUpdate(boolean insert, Connection con, Activity act) throws WTException {
		ActivityDAO dao = ActivityDAO.getInstance();
		
		OActivity oact = createOActivity(act);
		if (oact.getDomainId() == null) oact.setDomainId(getTargetProfileId().getDomainId());
		
		if (insert) {
			oact.setActivityId(dao.getSequence(con).intValue());
			oact.setRevisionStatus(EnumUtils.toSerializedName(Activity.RevisionStatus.MODIFIED));
			dao.insert(con, oact);
		} else {
			dao.update(con, oact);
		}
		
		return createActivity(oact);
	}
	
	private Causal doCausalUpdate(boolean insert, Connection con, Causal cau) throws WTException {
		CausalDAO dao = CausalDAO.getInstance();
		
		OCausal ocau = createOCausal(cau);
		if (ocau.getDomainId() == null) ocau.setDomainId(getTargetProfileId().getDomainId());
		
		if (insert) {
			ocau.setCausalId(dao.getSequence(con).intValue());
			ocau.setRevisionStatus(EnumUtils.toSerializedName(Causal.RevisionStatus.MODIFIED));
			dao.insert(con, ocau);
		} else {
			dao.update(con, ocau);
		}
		
		return createCausal(ocau);
	}
	
	private OActivity createOActivity(Activity cau) {
		if (cau == null) return null;
		OActivity ocau = new OActivity();
		ocau.setActivityId(cau.getActivityId());
		ocau.setDomainId(cau.getDomainId());
		ocau.setUserId(cau.getUserId());
		ocau.setRevisionStatus(EnumUtils.toSerializedName(cau.getRevisionStatus()));
		ocau.setDescription(cau.getDescription());
		ocau.setReadOnly(cau.getReadOnly());
		ocau.setExternalId(cau.getExternalId());
		return ocau;
	}
	
	private Activity createActivity(OActivity oact) {
		if (oact == null) return null;
		Activity act = new Activity();
		act.setActivityId(oact.getActivityId());
		act.setDomainId(oact.getDomainId());
		act.setUserId(oact.getUserId());
		act.setRevisionStatus(EnumUtils.forSerializedName(oact.getRevisionStatus(), Activity.RevisionStatus.class));
		act.setDescription(oact.getDescription());
		act.setReadOnly(oact.getReadOnly());
		act.setExternalId(oact.getExternalId());
		return act;
	}
	
	private OCausal createOCausal(Causal cau) {
		if (cau == null) return null;
		OCausal ocau = new OCausal();
		ocau.setCausalId(cau.getCausalId());
		ocau.setDomainId(cau.getDomainId());
		ocau.setUserId(cau.getUserId());
		ocau.setMasterDataId(cau.getMasterDataId());
		ocau.setRevisionStatus(EnumUtils.toSerializedName(cau.getRevisionStatus()));
		ocau.setDescription(cau.getDescription());
		ocau.setReadOnly(cau.getReadOnly());
		ocau.setExternalId(cau.getExternalId());
		return ocau;
	}
	
	private Causal createCausal(OCausal ocau) {
		if (ocau == null) return null;
		Causal cau = new Causal();
		cau.setCausalId(ocau.getCausalId());
		cau.setDomainId(ocau.getDomainId());
		cau.setUserId(ocau.getUserId());
		cau.setMasterDataId(ocau.getMasterDataId());
		cau.setRevisionStatus(EnumUtils.forSerializedName(ocau.getRevisionStatus(), Causal.RevisionStatus.class));
		cau.setDescription(ocau.getDescription());
		cau.setReadOnly(ocau.getReadOnly());
		cau.setExternalId(ocau.getExternalId());
		return cau;
	}
	
	private CausalExt createCausalExt(VCausal vcau) {
		if (vcau == null) return null;
		CausalExt cau = new CausalExt();
		cau.setCausalId(vcau.getCausalId());
		cau.setDomainId(vcau.getDomainId());
		cau.setUserId(vcau.getUserId());
		cau.setMasterDataId(vcau.getMasterDataId());
		cau.setRevisionStatus(EnumUtils.forSerializedName(vcau.getRevisionStatus(), Causal.RevisionStatus.class));
		cau.setDescription(vcau.getDescription());
		cau.setReadOnly(vcau.getReadOnly());
		cau.setExternalId(vcau.getExternalId());
		cau.setMasterDataDescription(vcau.getMasterDataDescription());
		return cau;
	}
	
	private OIMChat createOIMChat(IMChat cha) {
		if (cha == null) return null;
		OIMChat ocha = new OIMChat();
		ocha.setId(cha.getId());
		ocha.setDomainId(cha.getDomainId());
		ocha.setUserId(cha.getUserId());
		ocha.setChatJid(cha.getChatJid());
		ocha.setRevisionStatus(EnumUtils.toSerializedName(cha.getRevisionStatus()));
		ocha.setRevisionTimestamp(cha.getRevisionTimestamp());
		ocha.setOwnerJid(cha.getOwnerJid());
		ocha.setName(cha.getName());
		ocha.setIsGroupChat(cha.getIsGroupChat());
		ocha.setLastSeenActivity(cha.getLastSeenActivity());
		ocha.setWithJid(cha.getWithJid());
		return ocha;
	}
	
	private IMChat createIMChat(OIMChat ocha) {
		if (ocha == null) return null;
		IMChat cha = new IMChat();
		cha.setId(ocha.getId());
		cha.setDomainId(ocha.getDomainId());
		cha.setUserId(ocha.getUserId());
		cha.setRevisionStatus(EnumUtils.forSerializedName(ocha.getRevisionStatus(), IMChat.RevisionStatus.class));
		cha.setRevisionTimestamp(ocha.getRevisionTimestamp());
		cha.setChatJid(ocha.getChatJid());
		cha.setOwnerJid(ocha.getOwnerJid());
		cha.setName(ocha.getName());
		cha.setIsGroupChat(ocha.getIsGroupChat());
		cha.setLastSeenActivity(ocha.getLastSeenActivity());
		cha.setWithJid(ocha.getWithJid());
		return cha;
	}
	
	private OIMMessage createOIMMessage(IMMessage mes) {
		if (mes == null) return null;
		OIMMessage omes = new OIMMessage();
		omes.setId(mes.getId());
		omes.setDomainId(mes.getDomainId());
		omes.setUserId(mes.getUserId());
		omes.setChatJid(mes.getChatJid());
		omes.setSenderJid(mes.getSenderJid());
		omes.setSenderResource(mes.getSenderResource());
		omes.setTimestamp(mes.getTimestamp());
		omes.setDeliveryTimestamp(mes.getDeliveryTimestamp());
		omes.setAction(EnumUtils.toSerializedName(mes.getAction()));
		omes.setText(mes.getText());
		omes.setData(mes.getData());
		omes.setMessageUid(mes.getMessageUid());
		omes.setStanzaId(mes.getStanzaId());
		return omes;
	}
	
	private IMMessage createIMMessage(OIMMessage omes) {
		if (omes == null) return null;
		IMMessage mes = new IMMessage();
		mes.setId(omes.getId());
		mes.setDomainId(omes.getDomainId());
		mes.setUserId(omes.getUserId());
		mes.setChatJid(omes.getChatJid());
		mes.setSenderJid(omes.getSenderJid());
		mes.setSenderResource(omes.getSenderResource());
		mes.setTimestamp(omes.getTimestamp());
		mes.setDeliveryTimestamp(omes.getDeliveryTimestamp());
		mes.setAction(EnumUtils.forSerializedName(omes.getAction(), IMMessage.Action.class));
		mes.setText(omes.getText());
		mes.setData(omes.getData());
		mes.setMessageUid(omes.getMessageUid());
		mes.setStanzaId(omes.getStanzaId());
		return mes;
	}
	
	private DateTime createRevisionTimestamp() {
		return DateTime.now(DateTimeZone.UTC);
	}
}
