/* ********************************************************************
Licensed to Jasig under one or more contributor license
agreements. See the NOTICE file distributed with this work
for additional information regarding copyright ownership.
Jasig licenses this file to you under the Apache License,
Version 2.0 (the "License"); you may not use this file
except in compliance with the License. You may obtain a
copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.bedework.calsvc;
import org.bedework.access.Access;
import org.bedework.access.Ace;
import org.bedework.access.AceWho;
import org.bedework.access.Acl.CurrentAccess;
import org.bedework.access.PrivilegeSet;
import org.bedework.calcorei.Calintf;
import org.bedework.calcorei.CalintfFactory;
import org.bedework.caldav.util.notifications.admin.AdminNoteParsers;
import org.bedework.caldav.util.notifications.eventreg.EventregParsers;
import org.bedework.caldav.util.notifications.suggest.SuggestParsers;
import org.bedework.calfacade.BwCalendar;
import org.bedework.calfacade.BwCategory;
import org.bedework.calfacade.BwContact;
import org.bedework.calfacade.BwEventProperty;
import org.bedework.calfacade.BwGroup;
import org.bedework.calfacade.BwLocation;
import org.bedework.calfacade.BwPrincipal;
import org.bedework.calfacade.BwPrincipalInfo;
import org.bedework.calfacade.BwResource;
import org.bedework.calfacade.BwStats;
import org.bedework.calfacade.BwStats.CacheStats;
import org.bedework.calfacade.BwStats.StatsEntry;
import org.bedework.calfacade.BwString;
import org.bedework.calfacade.RecurringRetrievalMode;
import org.bedework.calfacade.base.BwDbentity;
import org.bedework.calfacade.base.BwShareableDbentity;
import org.bedework.calfacade.base.BwUnversionedDbentity;
import org.bedework.calfacade.base.UpdateFromTimeZonesInfo;
import org.bedework.calfacade.configs.AuthProperties;
import org.bedework.calfacade.configs.BasicSystemProperties;
import org.bedework.calfacade.configs.Configurations;
import org.bedework.calfacade.configs.IndexProperties;
import org.bedework.calfacade.configs.NotificationProperties;
import org.bedework.calfacade.configs.SystemProperties;
import org.bedework.calfacade.exc.CalFacadeAccessException;
import org.bedework.calfacade.exc.CalFacadeException;
import org.bedework.calfacade.filter.SimpleFilterParser;
import org.bedework.calfacade.ifs.Directories;
import org.bedework.calfacade.ifs.IfInfo;
import org.bedework.calfacade.indexing.BwIndexer;
import org.bedework.calfacade.mail.MailerIntf;
import org.bedework.calfacade.svc.BwAuthUser;
import org.bedework.calfacade.svc.BwCalSuite;
import org.bedework.calfacade.svc.BwPreferences;
import org.bedework.calfacade.svc.BwView;
import org.bedework.calfacade.svc.EventInfo;
import org.bedework.calfacade.svc.PrincipalInfo;
import org.bedework.calfacade.svc.UserAuth;
import org.bedework.calfacade.svc.wrappers.BwCalSuiteWrapper;
import org.bedework.calfacade.util.CalFacadeUtil;
import org.bedework.calsvc.scheduling.Scheduling;
import org.bedework.calsvc.scheduling.SchedulingIntf;
import org.bedework.calsvci.AdminI;
import org.bedework.calsvci.CalSuitesI;
import org.bedework.calsvci.CalSvcFactoryDefault;
import org.bedework.calsvci.CalSvcI;
import org.bedework.calsvci.CalSvcIPars;
import org.bedework.calsvci.CalendarsI;
import org.bedework.calsvci.Categories;
import org.bedework.calsvci.Contacts;
import org.bedework.calsvci.DumpIntf;
import org.bedework.calsvci.EventsI;
import org.bedework.calsvci.FiltersI;
import org.bedework.calsvci.Locations;
import org.bedework.calsvci.NotificationsI;
import org.bedework.calsvci.PreferencesI;
import org.bedework.calsvci.ResourcesI;
import org.bedework.calsvci.RestoreIntf;
import org.bedework.calsvci.SchedulingI;
import org.bedework.calsvci.SharingI;
import org.bedework.calsvci.SynchI;
import org.bedework.calsvci.SynchReport;
import org.bedework.calsvci.SynchReportItem;
import org.bedework.calsvci.SysparsI;
import org.bedework.calsvci.TimeZonesStoreI;
import org.bedework.calsvci.UsersI;
import org.bedework.calsvci.ViewsI;
import org.bedework.icalendar.IcalCallback;
import org.bedework.sysevents.events.SysEvent;
import org.bedework.sysevents.events.SysEventBase;
import org.bedework.util.jmx.MBeanUtil;
import org.bedework.util.misc.Util;
import org.bedework.util.security.PwEncryptionIntf;
import org.bedework.util.security.keys.GenKeysMBean;
import org.bedework.util.timezones.Timezones;
import org.apache.log4j.Logger;
import org.hibernate.exception.ConstraintViolationException;
import java.sql.Blob;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/** This is an implementation of the service level interface to the calendar
* suite.
*
* @author Mike Douglass douglm rpi.edu
*/
public class CalSvc extends CalSvcI {
//private String systemName;
private CalSvcIPars pars;
private boolean debug;
private static Configurations configs;
static {
try {
configs = new CalSvcFactoryDefault().getSystemConfig();
new SuggestParsers(); // force load
new EventregParsers(); // force load
new AdminNoteParsers(); // force load
} catch (final Throwable t) {
t.printStackTrace();
}
}
private boolean open;
//private boolean superUser;
/** True if this is a session to create a new account. Do not try to create one
*/
private boolean creating;
private boolean authenticated;
/* Information about current user */
private PrincipalInfo principalInfo;
/* The account that we are representing
*/
// private BwUser currentUser;
/* The account we logged in as - for user access equals currentUser, for admin
* access currentUser is the group we are managing.
*/
// private BwUser currentAuthUser;
/* If we're doing admin this is the authorised user entry
*/
BwAuthUser adminUser;
/* ....................... Handlers ..................................... */
//private MailerIntf mailer;
private PreferencesI prefsHandler;
private AdminI adminHandler;
private EventsI eventsHandler;
private FiltersI filtersHandler;
private CalendarsI calendarsHandler;
private SysparsI sysparsHandler;
private CalSuitesI calSuitesHandler;
private NotificationsI notificationsHandler;
private ResourcesI resourcesHandler;
private SchedulingIntf sched;
private SharingI sharingHandler;
private SynchI synch;
private UsersI usersHandler;
private ViewsI viewsHandler;
private Categories categoriesHandler;
private Locations locationsHandler;
private Contacts contactsHandler;
private final Collection<CalSvcDb> handlers = new ArrayList<>();
/* ....................... ... ..................................... */
/** Core calendar interface
*/
private transient Calintf cali;
private transient PwEncryptionIntf pwEncrypt;
/** handles timezone info.
*/
private Timezones timezones;
private TimeZonesStoreI tzstore;
/* null if timezones not initialised */
private static String tzserverUri = null;
/** The user authorisation object
*/
private UserAuth userAuth;
private transient UserAuth.CallBack uacb;
private transient Directories.CallBack gcb;
/** The user groups object.
*/
private Directories userGroups;
/** The admin groups object.
*/
private Directories adminGroups;
private IcalCallback icalcb;
private transient Logger log;
@Override
public void init(final CalSvcIPars parsParam) throws CalFacadeException {
init(parsParam, false);
}
private void init(final CalSvcIPars parsParam,
final boolean creating) throws CalFacadeException {
pars = (CalSvcIPars)parsParam.clone();
this.creating = creating;
debug = getLogger().isDebugEnabled();
final long start = System.currentTimeMillis();
try {
if (configs == null) {
// Try again - failed at static init?
configs = new CalSvcFactoryDefault().getSystemConfig();
}
open();
beginTransaction();
if (userGroups != null) {
userGroups.init(getGroupsCallBack(),
configs);
}
if (adminGroups != null) {
adminGroups.init(getGroupsCallBack(),
configs);
}
final SystemProperties sp = getSystemProperties();
if (tzserverUri == null) {
tzserverUri = sp.getTzServeruri();
if (tzserverUri == null) {
throw new CalFacadeException("No timezones server URI defined");
}
Timezones.initTimezones(tzserverUri);
Timezones.setSystemDefaultTzid(sp.getTzid());
}
/* Some checks on parameter validity
*/
// BwUser =
tzstore = new TimeZonesStoreImpl(this);
/* Nominate our timezone registry */
System.setProperty("net.fortuna.ical4j.timezone.registry",
"org.bedework.icalendar.TimeZoneRegistryFactoryImpl");
if (!creating) {
final String tzid = getPrefsHandler().get().getDefaultTzid();
if (tzid != null) {
Timezones.setThreadDefaultTzid(tzid);
}
// if (pars.getCaldav() && !pars.isGuest()) {
if (!pars.isGuest()) {
/* Ensure scheduling resources exist */
// getCal().getSpecialCalendar(getPrincipal(), BwCalendar.calTypeInbox,
// true, PrivilegeDefs.privAny);
// getCal().getSpecialCalendar(getPrincipal(), BwCalendar.calTypeOutbox,
// true, PrivilegeDefs.privAny);
}
}
if ((pars.getPublicAdmin() || pars.getAllowSuperUser()) &&
(pars.getAuthUser() != null)) {
((SvciPrincipalInfo)principalInfo).setSuperUser(
getSysparsHandler().isRootUser(principalInfo.getAuthPrincipal()));
}
postNotification(
SysEvent.makePrincipalEvent(SysEvent.SysCode.USER_SVCINIT,
getPrincipal(),
System.currentTimeMillis() - start));
} catch (final CalFacadeException cfe) {
rollbackTransaction();
cfe.printStackTrace();
throw cfe;
} catch (final Throwable t) {
rollbackTransaction();
t.printStackTrace();
throw new CalFacadeException(t);
} finally {
try {
endTransaction();
} catch (final Throwable ignored) {}
try {
close();
} catch (final Throwable ignored) {}
}
}
@Override
public BasicSystemProperties getBasicSystemProperties() throws CalFacadeException {
return configs.getBasicSystemProperties();
}
@Override
public AuthProperties getAuthProperties() throws CalFacadeException {
return configs.getAuthProperties(authenticated);
}
@Override
public AuthProperties getAuthProperties(final boolean auth) throws CalFacadeException {
return configs.getAuthProperties(auth);
}
@Override
public SystemProperties getSystemProperties() throws CalFacadeException {
return configs.getSystemProperties();
}
@Override
public NotificationProperties getNotificationProperties()
throws CalFacadeException {
return configs.getNotificationProps();
}
@Override
public IndexProperties getIndexProperties() throws CalFacadeException {
return configs.getIndexProperties();
}
@Override
public void setCalSuite(final String name) throws CalFacadeException {
final BwCalSuiteWrapper cs = getCalSuitesHandler().get(name);
if (cs == null) {
error("******************************************************");
error("Unable to fetch calendar suite " + name);
error("Is the database correctly initialised?");
error("******************************************************");
throw new CalFacadeException(CalFacadeException.unknownCalsuite,
name);
}
getCalSuitesHandler().set(cs);
/* This is wrong. The calsuite doesn't always represent the group
It may be a sub-group.
final BwPrincipal user = getUsersHandler().getPrincipal(cs.getGroup().getOwnerHref());
user.setGroups(getDirectories().getAllGroups(user));
if (!user.equals(principalInfo.getPrincipal())) {
((SvciPrincipalInfo)principalInfo).setPrincipal(user);
}
*/
}
@Override
public PrincipalInfo getPrincipalInfo() {
return principalInfo;
}
@Override
public boolean getSuperUser() {
return principalInfo.getSuperUser();
}
@Override
public byte[] getPublicKey(final String domain,
final String service) throws CalFacadeException {
try {
return getEncrypter().getPublicKey();
} catch (final Throwable t) {
throw new CalFacadeException(t);
}
}
@Override
public BwStats getStats() throws CalFacadeException {
final BwStats stats = getCal().getStats();
if (timezones != null) {
final CacheStats cs = stats.getDateCacheStats();
cs.setHits(timezones.getDateCacheHits());
cs.setMisses(timezones.getDateCacheMisses());
cs.setCached(timezones.getDatesCached());
}
stats.setAccessStats(Access.getStatistics());
return stats;
}
@Override
public void setDbStatsEnabled(final boolean enable) throws CalFacadeException {
//if (!pars.getPublicAdmin()) {
// throw new CalFacadeAccessException();
//}
getCal().setDbStatsEnabled(enable);
}
@Override
public boolean getDbStatsEnabled() throws CalFacadeException {
return getCal().getDbStatsEnabled();
}
@Override
public void dumpDbStats() throws CalFacadeException {
//if (!pars.getPublicAdmin()) {
// throw new CalFacadeAccessException();
//}
trace(getStats().toString());
getCal().dumpDbStats();
}
@Override
public Collection<StatsEntry> getDbStats() throws CalFacadeException {
//if (!pars.getPublicAdmin()) {
// throw new CalFacadeAccessException();
//}
return getCal().getDbStats();
}
@Override
public void logStats() throws CalFacadeException {
logIt(getStats().toString());
}
@Override
public IfInfo getIfInfo() throws CalFacadeException {
return getCal().getIfInfo();
}
@Override
public List<IfInfo> getActiveIfInfos() throws CalFacadeException {
final List<IfInfo> ifs = new ArrayList<>();
for (final Calintf ci: getCal().active()) {
ifs.add(ci.getIfInfo());
}
return ifs;
}
@Override
public void kill(final IfInfo ifInfo) {
// We could probably use some sort of kill listener to clean up after this
try {
for (final Calintf ci: getCal().active()) {
final IfInfo calIfInfo = ci.getIfInfo();
if (calIfInfo.getId().equals(ifInfo.getId())) {
warn("Stopping interface with id " + ifInfo.getId());
ci.kill();
break;
}
}
} catch (final Throwable t) {
error(t);
}
}
@Override
public void setState(final String val) throws CalFacadeException {
getCal().setState(val);
}
@Override
public void postNotification(final SysEventBase ev) throws CalFacadeException {
getCal().postNotification(ev);
}
@Override
public void flushAll() throws CalFacadeException {
getCal().flush();
}
@Override
public void open() throws CalFacadeException {
//TimeZoneRegistryImpl.setThreadCb(getIcalCallback());
if (open) {
return;
}
open = true;
getCal().open(pars.getWebMode(),
pars.getForRestore(),
pars.getIndexRebuild());
for (final CalSvcDb handler: handlers) {
handler.open();
}
}
@Override
public boolean isOpen() {
return open;
}
@Override
public boolean isRolledback() throws CalFacadeException {
if (!open) {
return false;
}
return getCal().isRolledback();
}
@Override
public void close() throws CalFacadeException {
open = false;
getCal().close();
for (final CalSvcDb handler: handlers) {
handler.close();
}
}
@Override
public void beginTransaction() throws CalFacadeException {
getCal().beginTransaction();
}
@Override
public void endTransaction() throws CalFacadeException {
getCal().endTransaction();
}
@Override
public void rollbackTransaction() throws CalFacadeException {
getCal().rollbackTransaction();
}
@Override
public Timestamp getCurrentTimestamp() throws CalFacadeException {
return getCal().getCurrentTimestamp();
}
public Blob getBlob(final byte[] val) throws CalFacadeException {
return getCal().getBlob(val);
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#reAttach(org.bedework.calfacade.base.BwDbentity)
*/
@Override
public void reAttach(final BwDbentity val) throws CalFacadeException {
getCal().reAttach(val);
}
@Override
public BwUnversionedDbentity merge(final BwUnversionedDbentity val) throws CalFacadeException {
return getCal().merge(val);
}
@Override
public IcalCallback getIcalCallback() {
if (icalcb == null) {
icalcb = new IcalCallbackcb();
}
return icalcb;
}
/* ====================================================================
* Factory methods
* ==================================================================== */
@Override
public DumpIntf getDumpHandler() throws CalFacadeException {
return new DumpImpl(this);
}
@Override
public RestoreIntf getRestoreHandler() throws CalFacadeException {
return new RestoreImpl(this);
}
class SvcSimpleFilterParser extends SimpleFilterParser {
@Override
public BwCalendar getCollection(final String path)
throws CalFacadeException {
return getCalendarsHandler().get(path);
}
@Override
public BwCalendar resolveAlias(final BwCalendar val,
final boolean resolveSubAlias)
throws CalFacadeException {
return getCalendarsHandler().resolveAlias(val, resolveSubAlias, false);
}
@Override
public Collection<BwCalendar> getChildren(final BwCalendar col)
throws CalFacadeException {
return getCalendarsHandler().getChildren(col);
}
@Override
public BwCategory getCategoryByName(final String name) throws CalFacadeException {
return getCategoriesHandler().find(new BwString(null, name));
}
@Override
public BwCategory getCategory(final String uid) throws CalFacadeException {
return getCategoriesHandler().get(uid);
}
@Override
public BwView getView(final String path)
throws CalFacadeException {
return getViewsHandler().find(path);
}
@Override
public Collection<BwCalendar> decomposeVirtualPath(final String vpath)
throws CalFacadeException {
return getCalendarsHandler().decomposeVirtualPath(vpath);
}
@Override
public SimpleFilterParser getParser() throws CalFacadeException {
return new SvcSimpleFilterParser();
}
}
@Override
public SimpleFilterParser getFilterParser() {
return new SvcSimpleFilterParser();
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getSysparsHandler()
*/
@Override
public SysparsI getSysparsHandler() throws CalFacadeException {
if (sysparsHandler == null) {
sysparsHandler = new Syspars(this);
handlers.add((CalSvcDb)sysparsHandler);
}
return sysparsHandler;
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getMailer()
*/
@Override
public MailerIntf getMailer() throws CalFacadeException {
/*
if (mailer != null) {
return mailer;
}*/
try {
final MailerIntf mailer =
(MailerIntf)CalFacadeUtil.getObject(getSystemProperties().getMailerClass(),
MailerIntf.class);
mailer.init(configs.getMailConfigProperties());
return mailer;
} catch (final Throwable t) {
throw new CalFacadeException(t);
}
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getPrefsHandler()
*/
@Override
public PreferencesI getPrefsHandler() throws CalFacadeException {
if (prefsHandler == null) {
prefsHandler = new Preferences(this);
handlers.add((CalSvcDb)prefsHandler);
}
return prefsHandler;
}
@Override
public AdminI getAdminHandler() throws CalFacadeException {
if (!isPublicAdmin()) {
throw new CalFacadeAccessException();
}
if (adminHandler == null) {
adminHandler = new Admin(this);
handlers.add((CalSvcDb)adminHandler);
}
return adminHandler;
}
@Override
public EventsI getEventsHandler() throws CalFacadeException {
if (eventsHandler == null) {
eventsHandler = new Events(this);
handlers.add((CalSvcDb)eventsHandler);
}
return eventsHandler;
}
@Override
public FiltersI getFiltersHandler() {
if (filtersHandler == null) {
filtersHandler = new Filters(this);
handlers.add((CalSvcDb)filtersHandler);
}
return filtersHandler;
}
@Override
public CalendarsI getCalendarsHandler() throws CalFacadeException {
if (calendarsHandler == null) {
calendarsHandler = new Calendars(this);
handlers.add((CalSvcDb)calendarsHandler);
}
return calendarsHandler;
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getCalSuitesHandler()
*/
@Override
public CalSuitesI getCalSuitesHandler() throws CalFacadeException {
if (calSuitesHandler == null) {
calSuitesHandler = new CalSuites(this);
handlers.add((CalSvcDb)calSuitesHandler);
}
return calSuitesHandler;
}
public BwIndexer getIndexer() throws CalFacadeException {
return getIndexer(getPars().getPublicAdmin() ||
getPars().isGuest());
}
@Override
public BwIndexer getIndexer(final boolean publick) throws CalFacadeException {
if (publick) {
return getCal().getPublicIndexer();
}
return getCal().getIndexer(getPrincipal());
}
@Override
public BwIndexer getIndexer(final String principal) throws CalFacadeException {
final BwPrincipal pr;
if (principal == null) {
pr = getPrincipal();
} else {
pr = getPrincipal(principal);
}
return getCal().getIndexer(pr);
}
@Override
public BwIndexer getIndexer(final String principal,
final String indexRoot) throws CalFacadeException {
final BwPrincipal pr;
if (principal == null) {
pr = getPrincipal();
} else {
pr = getPrincipal(principal);
}
return getCal().getIndexer(pr, indexRoot);
}
@Override
public NotificationsI getNotificationsHandler() throws CalFacadeException {
if (notificationsHandler == null) {
notificationsHandler = new Notifications(this);
handlers.add((CalSvcDb)notificationsHandler);
}
return notificationsHandler;
}
@Override
public ResourcesI getResourcesHandler() throws CalFacadeException {
if (resourcesHandler == null) {
resourcesHandler = new ResourcesImpl(this);
handlers.add((CalSvcDb)resourcesHandler);
}
return resourcesHandler;
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getScheduler()
*/
@Override
public SchedulingI getScheduler() throws CalFacadeException {
if (sched == null) {
sched = new Scheduling(this);
handlers.add((CalSvcDb)sched);
}
return sched;
}
@Override
public SharingI getSharingHandler() throws CalFacadeException {
if (sharingHandler == null) {
sharingHandler = new Sharing(this);
handlers.add((CalSvcDb)sharingHandler);
}
return sharingHandler;
}
@Override
public SynchI getSynch() throws CalFacadeException {
if (synch == null) {
try {
synch = new Synch(this, configs.getSynchConfig());
handlers.add((CalSvcDb)synch);
} catch (Throwable t) {
throw new CalFacadeException(t);
}
}
return synch;
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getUsersHandler()
*/
@Override
public UsersI getUsersHandler() throws CalFacadeException {
if (usersHandler == null) {
usersHandler = new Users(this);
handlers.add((CalSvcDb)usersHandler);
}
return usersHandler;
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getViewsHandler()
*/
@Override
public ViewsI getViewsHandler() throws CalFacadeException {
if (viewsHandler == null) {
viewsHandler = new Views(this);
handlers.add((CalSvcDb)viewsHandler);
}
return viewsHandler;
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getDirectories()
*/
@Override
public Directories getDirectories() throws CalFacadeException {
if (isPublicAdmin()) {
return getAdminDirectories();
}
return getUserDirectories();
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getUserDirectories()
*/
@Override
public Directories getUserDirectories() throws CalFacadeException {
if (userGroups != null) {
return userGroups;
}
try {
userGroups = (Directories)CalFacadeUtil.getObject(getSystemProperties().getUsergroupsClass(), Directories.class);
userGroups.init(getGroupsCallBack(),
configs);
} catch (Throwable t) {
throw new CalFacadeException(t);
}
return userGroups;
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getAdminDirectories()
*/
@Override
public Directories getAdminDirectories() throws CalFacadeException {
if (adminGroups != null) {
return adminGroups;
}
try {
adminGroups = (Directories)CalFacadeUtil.getObject(getSystemProperties().getAdmingroupsClass(), Directories.class);
adminGroups.init(getGroupsCallBack(),
configs);
} catch (Throwable t) {
throw new CalFacadeException(t);
}
return adminGroups;
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getCategoriesHandler()
*/
@Override
public Categories getCategoriesHandler() throws CalFacadeException {
if (categoriesHandler == null) {
categoriesHandler = new CategoriesImpl(this);
categoriesHandler.init(pars.getAdminCanEditAllPublicCategories());
handlers.add((CalSvcDb)categoriesHandler);
}
return categoriesHandler;
}
@Override
public Locations getLocationsHandler() throws CalFacadeException {
if (locationsHandler == null) {
locationsHandler = new LocationsImpl(this);
locationsHandler.init(pars.getAdminCanEditAllPublicLocations());
handlers.add((CalSvcDb)locationsHandler);
}
return locationsHandler;
}
@Override
public Contacts getContactsHandler() throws CalFacadeException {
if (contactsHandler == null) {
contactsHandler = new ContactsImpl(this);
contactsHandler.init(pars.getAdminCanEditAllPublicContacts());
handlers.add((CalSvcDb)contactsHandler);
}
return contactsHandler;
}
/* ====================================================================
* Users and accounts
* ==================================================================== */
@Override
public BwPrincipal getPrincipal() throws CalFacadeException {
return principalInfo.getPrincipal();
}
@Override
public BwPrincipal getPrincipal(final String href) throws CalFacadeException {
return getCal().getPrincipal(href);
}
@Override
public UserAuth getUserAuth() throws CalFacadeException {
if (userAuth != null) {
return userAuth;
}
userAuth = (UserAuth)CalFacadeUtil.getObject(getSystemProperties().getUserauthClass(),
UserAuth.class);
userAuth.initialise(getUserAuthCallBack());
return userAuth;
}
@Override
public long getUserMaxEntitySize() throws CalFacadeException {
long max = getPrefsHandler().get().getMaxEntitySize();
if (max != 0) {
return max;
}
return getAuthProperties().getMaxUserEntitySize();
}
@Override
public BwPreferences getPreferences(final String principalHref) throws CalFacadeException {
return getCal().getPreferences(principalHref);
}
/* ====================================================================
* adminprefs
* ==================================================================== */
@Override
public void removeFromAllPrefs(final BwShareableDbentity val) throws CalFacadeException {
getCal().removeFromAllPrefs(val);
}
/* ====================================================================
* groups
* ==================================================================== */
@Override
public BwGroup findGroup(final String account,
final boolean admin) throws CalFacadeException {
return getCal().findGroup(account, admin);
}
/* ====================================================================
* Access
* ==================================================================== */
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#changeAccess(org.bedework.calfacade.base.BwShareableDbentity, java.util.Collection)
*/
@Override
public void changeAccess(BwShareableDbentity ent,
final Collection<Ace> aces,
final boolean replaceAll) throws CalFacadeException {
if (ent instanceof BwCalSuiteWrapper) {
ent = ((BwCalSuiteWrapper)ent).fetchEntity();
}
getCal().changeAccess(ent, aces, replaceAll);
if (ent instanceof BwCalendar) {
final BwCalendar col = (BwCalendar)ent;
if (col.getCalType() == BwCalendar.calTypeInbox) {
// Same access as inbox
final BwCalendar pendingInbox =
getCalendarsHandler().getSpecial(BwCalendar.calTypePendingInbox,
true);
if (pendingInbox == null) {
warn("Unable to update pending inbox access");
} else {
getCal().changeAccess(pendingInbox, aces, replaceAll);
}
}
((Preferences)getPrefsHandler()).updateAdminPrefs(false,
col,
null,
null,
null);
} else if (ent instanceof BwEventProperty) {
((Preferences)getPrefsHandler()).updateAdminPrefs(false,
(BwEventProperty)ent);
}
}
@Override
public void defaultAccess(BwShareableDbentity ent,
final AceWho who) throws CalFacadeException {
if (ent instanceof BwCalSuiteWrapper) {
ent = ((BwCalSuiteWrapper)ent).fetchEntity();
}
getCal().defaultAccess(ent, who);
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#checkAccess(org.bedework.calfacade.base.BwShareableDbentity, int, boolean)
*/
@Override
public CurrentAccess checkAccess(final BwShareableDbentity ent, final int desiredAccess,
final boolean returnResult) throws CalFacadeException {
return getCal().checkAccess(ent, desiredAccess, returnResult);
}
/* (non-Javadoc)
* @see org.bedework.calsvci.CalSvcI#getSynchReport(java.lang.String, java.lang.String, int, boolean)
*/
@Override
public SynchReport getSynchReport(final String path,
final String token,
final int limit,
final boolean recurse) throws CalFacadeException {
final BwCalendar col = getCalendarsHandler().get(path);
if (col == null) {
return null;
}
Set<SynchReportItem> items = new TreeSet<SynchReportItem>();
String resToken = getSynchItems(col, path, token, items, recurse);
final SynchReport res = new SynchReport(items, resToken);
if ((limit > 0) && (res.size() >= limit)) {
if (res.size() == limit) {
return res;
}
items = new TreeSet<>();
resToken = "";
for (final SynchReportItem item: res.getItems()) {
if (item.getToken().compareTo(resToken) > 0) {
resToken = item.getToken();
}
items.add(item);
if (items.size() == limit) {
break;
}
}
}
if (resToken.length() == 0) {
resToken = Util.icalUTCTimestamp() + "-0000";
}
return new SynchReport(items, resToken);
}
private boolean canSync(final BwCalendar col) {
//if (col.getCalType() == BwCalendar.calTypeAlias) {
// return false;
//}
if (col.getCalType() == BwCalendar.calTypeExtSub) {
return false;
}
return true;
}
/* ====================================================================
* Timezones
* ==================================================================== */
@Override
public UpdateFromTimeZonesInfo updateFromTimeZones(final String colHref,
final int limit,
final boolean checkOnly,
final UpdateFromTimeZonesInfo info
) throws CalFacadeException {
return tzstore.updateFromTimeZones(colHref, limit, checkOnly, info);
}
/* ====================================================================
* Get back end interface
* ==================================================================== */
/* This will get a calintf based on the supplied collection object.
*/
Calintf getCal(final BwCalendar cal) throws CalFacadeException {
return getCal();
}
/* We need to synchronize this code to prevent stale update exceptions.
* db locking might be better - this could still fail in a clustered
* environment for example.
*/
private static volatile Object synchlock = new Object();
/* Currently this gets a local calintf only. Later we need to use a par to
* get calintf from a table.
*/
Calintf getCal() throws CalFacadeException {
if (cali != null) {
return cali;
}
final long start = System.currentTimeMillis();
try {
final long beforeGetIntf = System.currentTimeMillis() - start;
cali = CalintfFactory.getIntf(CalintfFactory.hibernateClass);
final long afterGetIntf = System.currentTimeMillis() - start;
cali.open(pars.getWebMode(),
pars.getForRestore(),
pars.getIndexRebuild()); // Just for the user interactions
postNotification(SysEvent.makeTimedEvent("Login: about to obtain calintf",
beforeGetIntf));
postNotification(SysEvent.makeTimedEvent("Login: calintf obtained",
afterGetIntf));
postNotification(
SysEvent.makeTimedEvent("Login: intf opened",
System.currentTimeMillis() - start));
cali.beginTransaction();
postNotification(
SysEvent.makeTimedEvent("Login: transaction started",
System.currentTimeMillis() - start));
String runAsUser = pars.getUser();
if (pars.getCalSuite() != null) {
BwCalSuite cs = cali.getCalSuite(pars.getCalSuite());
if (cs == null) {
error("******************************************************");
error("Unable to fetch calendar suite " + pars.getCalSuite());
error("Is the database correctly initialised?");
error("******************************************************");
throw new CalFacadeException(CalFacadeException.unknownCalsuite,
pars.getCalSuite());
}
getCalSuitesHandler().set(new BwCalSuiteWrapper(cs));
/* For administrative use we use the account of the admin group the user
* is a direct member of
*
* For public clients we use the calendar suite owning group.
*/
if (!pars.getPublicAdmin()) {
runAsUser = cs.getGroup().getOwnerHref();
}
}
postNotification(SysEvent.makeTimedEvent("Login: before get dirs",
System.currentTimeMillis() - start));
Directories dir = getDirectories();
/* Get ourselves a user object */
String authenticatedUser = pars.getAuthUser();
if (authenticatedUser != null) {
String sv = authenticatedUser;
if (dir.isPrincipal(authenticatedUser)) {
authenticatedUser = dir.accountFromPrincipal(authenticatedUser);
}
if (authenticatedUser == null) {
error("Failed with Authenticated user " + sv);
return null;
}
if (authenticatedUser.endsWith("/")) {
getLogger().warn("Authenticated user " + authenticatedUser +
" ends with \"/\"");
}
}
postNotification(SysEvent.makeTimedEvent("Login: before user fetch",
System.currentTimeMillis() - start));
//synchronized (synchlock) {
final Users users = (Users)getUsersHandler();
if (runAsUser == null) {
runAsUser = authenticatedUser;
}
BwPrincipal currentPrincipal;
final BwPrincipal authPrincipal;
PrivilegeSet maxAllowedPrivs = null;
boolean subscriptionsOnly = getSystemProperties().getUserSubscriptionsOnly();
if (pars.getForRestore()) {
authenticated = true;
currentPrincipal = dir.caladdrToPrincipal(pars.getAuthUser());
authPrincipal = currentPrincipal;
subscriptionsOnly = false;
} else if (authenticatedUser == null) {
authenticated = false;
// Unauthenticated use
currentPrincipal = users.getUser(runAsUser);
if (currentPrincipal == null) {
// XXX Should we set this one up?
currentPrincipal = BwPrincipal.makeUserPrincipal();
}
currentPrincipal.setUnauthenticated(true);
authPrincipal = currentPrincipal;
maxAllowedPrivs = PrivilegeSet.readOnlyPrivileges;
} else {
authenticated = true;
currentPrincipal = users.getUser(authenticatedUser);
if (currentPrincipal == null) {
/* Add the user to the database. Presumably this is first logon
*/
getLogger().debug("Add new user " + authenticatedUser);
currentPrincipal = addUser(authenticatedUser);
if (currentPrincipal == null) {
error("Failed to find user after adding: " + authenticatedUser);
}
}
authPrincipal = currentPrincipal;
if (authenticatedUser.equals(runAsUser)) {
getLogger().debug("Authenticated user " + authenticatedUser +
" logged on");
} else {
currentPrincipal = users.getUser(runAsUser);
if (currentPrincipal == null) {
// throw new CalFacadeException("User " + runAsUser + " does not exist.");
/* Add the user to the database. Presumably this is first logon
*/
getLogger().debug("Add new run-as-user " + runAsUser);
currentPrincipal = addUser(runAsUser);
}
getLogger().debug("Authenticated user " + authenticatedUser +
" logged on - running as " + runAsUser);
}
currentPrincipal.setGroups(dir.getAllGroups(currentPrincipal));
postNotification(SysEvent.makeTimedEvent("Login: after get Groups",
System.currentTimeMillis() - start));
if (pars.getService()) {
subscriptionsOnly = false;
} else {
final BwPrincipalInfo bwpi = dir.getDirInfo(currentPrincipal);
currentPrincipal.setPrincipalInfo(bwpi);
if (pars.getPublicAdmin() || (bwpi != null && bwpi.getHasFullAccess())) {
subscriptionsOnly = false;
}
postNotification(SysEvent.makeTimedEvent("Login: got Dirinfo",
System.currentTimeMillis() - start));
}
}
principalInfo = new SvciPrincipalInfo(this,
currentPrincipal,
authPrincipal,
maxAllowedPrivs,
subscriptionsOnly);
cali.init(pars.getLogId(),
configs,
principalInfo,
null,
pars.getPublicAdmin(),
pars.getPublicSubmission(),
pars.getSessionsless());
if (!currentPrincipal.getUnauthenticated()) {
if (pars.getService()) {
postNotification(
SysEvent.makePrincipalEvent(SysEvent.SysCode.SERVICE_USER_LOGIN,
currentPrincipal,
System.currentTimeMillis() - start));
} else if (!creating) {
users.logon(currentPrincipal);
postNotification(
SysEvent.makePrincipalEvent(SysEvent.SysCode.USER_LOGIN,
currentPrincipal,
System.currentTimeMillis() - start));
}
} else {
// If we have a runAsUser it's a public client. Pretend we authenticated
// WHY? currentPrincipal.setUnauthenticated(runAsUser == null);
}
if (pars.getPublicAdmin() || pars.isGuest()) {
if (debug) {
trace("PublicAdmin: " + pars.getPublicAdmin() + " user: "
+ runAsUser);
}
/* We may be running as a different user. The preferences we want to see
* are those of the user we are running as - i.e. the 'run.as' user
* not those of the authenticated user.
* /
BwCalSuiteWrapper suite = getCalSuitesHandler().get();
BwPrincipal user;
if (suite != null) {
// Use this user
user = users.getPrincipal(suite.getGroup().getOwnerHref());
} else if (runAsUser == null) {
// Unauthenticated CalDAV for example?
user = currentPrincipal;
} else {
// No calendar suite set up
// XXX This is messy
if (runAsUser.startsWith("/")) {
user = users.getPrincipal(runAsUser);
} else {
user = users.getUser(runAsUser);
}
}
if (!user.equals(principalInfo.getPrincipal())) {
user.setGroups(getDirectories().getAllGroups(user));
user.setPrincipalInfo(getDirectories().getDirInfo(user));
((SvciPrincipalInfo)principalInfo).setPrincipal(user);
}
*/
}
return cali;
//}
} catch (final CalFacadeException cfe) {
error(cfe);
throw cfe;
} catch (final Throwable t) {
error(t);
throw new CalFacadeException(t);
} finally {
if (cali != null) {
cali.endTransaction();
cali.close();
//cali.flushAll();
}
}
}
void initPrincipal(final BwPrincipal p) throws CalFacadeException {
getCal().addNewCalendars(p);
}
/** Switch to the given principal to allow us to update their stuff - for
* example - send a notification.
*
* @param principal a principal object
*/
void pushPrincipal(final BwPrincipal principal) throws CalFacadeException {
BwPrincipal pr = getUsersHandler().getUser(principal.getPrincipalRef());
if (pr == null) {
pr = addUser(principal.getPrincipalRef());
}
((SvciPrincipalInfo)principalInfo).pushPrincipal(pr);
getCal().principalChanged();
}
/** Switch back to the previous principal.
*
* @throws CalFacadeException
*/
void popPrincipal() throws CalFacadeException {
((SvciPrincipalInfo)principalInfo).popPrincipal();
getCal().principalChanged();
}
/* Create the user. Get a new CalSvc object for that purpose.
*
*/
BwPrincipal addUser(final String val) throws CalFacadeException {
boolean closed = false;
final Users users = (Users)getUsersHandler();
/* Run this in a separate transaction to ensure we don't fail if the user
* gets created by a concurrent process.
*/
if (creating) {
// Get a fake user
return users.initUserObject(val);
}
final CalSvc nsvc = new CalSvc();
nsvc.init(pars, true);
try {
nsvc.open();
nsvc.beginTransaction();
final Users nusers = (Users)nsvc.getUsersHandler();
nusers.createUser(val);
} catch (final CalFacadeException cfe) {
if (cfe.getCause() instanceof ConstraintViolationException) {
// We'll assume it was created by another process.
warn("ConstraintViolationException trying to create " + val);
try {
nsvc.endTransaction();
} catch (final Throwable ignored) {}
try {
nsvc.close();
} catch (final Throwable ignored) {}
} else {
nsvc.rollbackTransaction();
if (debug) {
cfe.printStackTrace();
}
throw cfe;
}
closed = true;
} catch (final Throwable t) {
nsvc.rollbackTransaction();
if (debug) {
t.printStackTrace();
}
throw new CalFacadeException(t);
} finally {
if (!closed) {
try {
nsvc.endTransaction();
} catch (final CalFacadeException cfe) {
if (!(cfe.getCause() instanceof ConstraintViolationException)) {
throw cfe;
}
//Othewise we'll assume it was created by another process.
warn("ConstraintViolationException trying to create " + val);
} finally {
try {
nsvc.close();
} catch (final Throwable ignored) {
}
}
}
}
final BwPrincipal principal = users.getUser(val);
String caladdr = getDirectories().userToCaladdr(principal.getPrincipalRef());
if (caladdr != null) {
final List<String> emails = Collections.singletonList(caladdr.substring("mailto:".length()));
final Notifications notify = (Notifications)getNotificationsHandler();
notify.subscribe(principal, emails);
}
return principal;
}
private UserAuthCallBack getUserAuthCallBack() {
if (uacb == null) {
uacb = new UserAuthCallBack(this);
}
return (UserAuthCallBack)uacb;
}
private GroupsCallBack getGroupsCallBack() {
if (gcb == null) {
gcb = new GroupsCallBack(this);
}
return (GroupsCallBack)gcb;
}
private class IcalCallbackcb implements IcalCallback {
private int strictness = conformanceRelaxed;
@Override
public void setStrictness(final int val) throws CalFacadeException {
strictness = val;
}
@Override
public int getStrictness() throws CalFacadeException {
return strictness;
}
@Override
public BwPrincipal getPrincipal() throws CalFacadeException {
return CalSvc.this.getPrincipal();
}
@Override
public BwPrincipal getOwner() throws CalFacadeException {
if (isPublicAdmin()) {
return getUsersHandler().getPublicUser();
}
return CalSvc.this.getPrincipal();
}
@Override
public String getCaladdr(final String val) throws CalFacadeException {
return getDirectories().userToCaladdr(val);
}
@Override
public BwCategory findCategory(final BwString val) throws CalFacadeException {
return getCategoriesHandler().findPersistent(val);
}
@Override
public void addCategory(final BwCategory val) throws CalFacadeException {
getCategoriesHandler().add(val);
}
@Override
public BwContact getContact(final String uid) throws CalFacadeException {
return getContactsHandler().get(uid);
}
@Override
public BwContact findContact(final BwString val) throws CalFacadeException {
return getContactsHandler().findPersistent(val);
}
@Override
public void addContact(final BwContact val) throws CalFacadeException {
getContactsHandler().add(val);
}
@Override
public BwLocation getLocation(final String uid) throws CalFacadeException {
return getLocationsHandler().get(uid);
}
@Override
public BwLocation getLocation(final BwString address) throws CalFacadeException {
return getLocationsHandler().findPersistent(address);
}
@Override
public BwLocation findLocation(final BwString address) throws CalFacadeException {
final BwLocation loc = BwLocation.makeLocation();
loc.setAddress(address);
return getLocationsHandler().ensureExists(loc,
getOwner().getPrincipalRef()).entity;
}
@Override
public void addLocation(final BwLocation val) throws CalFacadeException {
getLocationsHandler().add(val);
}
@Override
public Collection getEvent(final String colPath,
final String guid)
throws CalFacadeException {
return getEventsHandler().getByUid(colPath, guid,
null,
RecurringRetrievalMode.overrides);
}
@Override
public boolean getTimezonesByReference() throws CalFacadeException {
return getSystemProperties().getTimezonesByReference();
}
}
private String getSynchItems(final BwCalendar col,
final String vpath,
final String token,
final Set<SynchReportItem> items,
final boolean recurse) throws CalFacadeException {
final Events eventsH = (Events)getEventsHandler();
final ResourcesImpl resourcesH = (ResourcesImpl)getResourcesHandler();
final Calendars colsH = (Calendars)getCalendarsHandler();
String newToken = "";
BwCalendar resolvedCol = col;
if (debug) {
trace("sync token: " + token + " col: " + resolvedCol.getPath());
}
if (col.getTombstoned()) {
return token;
}
if (col.getInternalAlias()) {
resolvedCol = getCalendarsHandler().resolveAlias(col, true, false);
}
if (resolvedCol.getTombstoned()) {
return token;
}
/* Each collection could be:
* a. A calendar collection or special - like Inbox -
* only need to look for events.
* b. Other collections. Need to look for events, resources and collections.
*/
final boolean eventsOnly = resolvedCol.getCollectionInfo().onlyCalEntities;
final Set<EventInfo> evs = eventsH.getSynchEvents(resolvedCol.getPath(), token);
for (final EventInfo ei: evs) {
// May be a filtered alias. Remove all those that aren't visible.
// TODO - if the filter changes this may result in an invalid response. Should force a resynch
// Could add an earliest valid sync token property.
// TODO - ALso tombstoned items need to be stored in the index.
// For the moment just let any tombstoned event through
if (!ei.getEvent().getTombstoned() &&
!eventsH.isVisible(col, ei.getEvent().getName())) {
continue;
}
final SynchReportItem sri = new SynchReportItem(vpath, ei);
items.add(sri);
if (sri.getToken().compareTo(newToken) > 0) {
newToken = sri.getToken();
}
}
if (!eventsOnly) {
// Look for resources
final List<BwResource> ress = resourcesH.getSynchResources(resolvedCol.getPath(), token);
for (final BwResource r: ress) {
final SynchReportItem sri = new SynchReportItem(vpath, r);
items.add(sri);
if (sri.getToken().compareTo(newToken) > 0) {
newToken = sri.getToken();
}
}
}
final Set<SynchReportItem> colItems = new TreeSet<>();
final Set<BwCalendar> cols = colsH.getSynchCols(resolvedCol.getPath(), token);
final List<BwCalendar> aliases = new ArrayList<>();
for (final BwCalendar c: cols) {
final int calType = c.getCalType();
if (calType == BwCalendar.calTypePendingInbox) {
continue;
}
if ((token != null) && (calType == BwCalendar.calTypeAlias)) {
aliases.add(c);
continue;
}
final SynchReportItem sri = new SynchReportItem(vpath, c, canSync(c));
colItems.add(sri);
items.add(sri);
if (sri.getToken().compareTo(newToken) > 0) {
newToken = sri.getToken();
}
if (debug) {
trace(" token=" + sri.getToken() + " for " + c.getPath());
}
}
if (!Util.isEmpty(aliases)) {
/* Resolve each one and see if the target is a candidate
*/
for (final BwCalendar c: aliases) {
final BwCalendar resolved = getCalendarsHandler().resolveAlias(c, true, false);
if (resolved == null) {
continue;
}
if (c.getTombstoned() && !getCal().testSynchCol(c, token)) {
continue;
}
if (!getCal().testSynchCol(resolved, token)) {
continue;
}
final SynchReportItem sri = new SynchReportItem(vpath,
c,
canSync(c),
resolved.getLastmod().getTagValue());
colItems.add(sri);
items.add(sri);
if (sri.getToken().compareTo(newToken) > 0) {
newToken = sri.getToken();
}
}
}
if (!recurse) {
return newToken;
}
if (Util.isEmpty(colItems)) {
return newToken;
}
for (final SynchReportItem sri: colItems) {
if (!sri.getCanSync()) {
continue;
}
final BwCalendar sricol = sri.getCol();
final String t = getSynchItems(sricol,
Util.buildPath(true, vpath, "/", sricol.getName()),
token, items, true);
if (t.compareTo(newToken) > 0) {
newToken = t;
}
}
return newToken;
}
/* ====================================================================
* Package private methods
* ==================================================================== */
void touchCalendar(final BwCalendar col) throws CalFacadeException {
getCal().touchCalendar(col);
}
PwEncryptionIntf getEncrypter() throws CalFacadeException {
if (pwEncrypt != null) {
return pwEncrypt;
}
try {
String pwEncryptClass = "org.bedework.util.security.PwEncryptionDefault";
//String pwEncryptClass = getSysparsHandler().get().getPwEncryptClass();
pwEncrypt = (PwEncryptionIntf)CalFacadeUtil.getObject(pwEncryptClass,
PwEncryptionIntf.class);
String privKeys = null;
String pubKeys = null;
GenKeysMBean gk = (GenKeysMBean)MBeanUtil.getMBean(GenKeysMBean.class,
GenKeysMBean.serviceName);
if (gk != null) {
privKeys = gk.getPrivKeyFileName();
pubKeys = gk.getPublicKeyFileName();
}
if (privKeys == null) {
throw new CalFacadeException("Unable to get keyfile locations. Is genkeys service installed?");
}
pwEncrypt.init(privKeys, pubKeys);
return pwEncrypt;
} catch (CalFacadeException cfe) {
cfe.printStackTrace();
throw cfe;
} catch (Throwable t) {
t.printStackTrace();
throw new CalFacadeException(t);
}
}
/* Get current parameters
*/
CalSvcIPars getPars() {
return pars;
}
/* See if in public admin mode
*/
private boolean isPublicAdmin() throws CalFacadeException {
return pars.getPublicAdmin();
}
/* Get a logger for messages
*/
private Logger getLogger() {
if (log == null) {
log = Logger.getLogger(this.getClass());
}
return log;
}
private void logIt(final String msg) {
getLogger().info(msg);
}
private void trace(final String msg) {
getLogger().debug(msg);
}
private void warn(final String msg) {
getLogger().warn(msg);
}
private void error(final String msg) {
getLogger().error(msg);
}
private void error(final Throwable t) {
getLogger().error(this, t);
}
}