Java tutorial
/*--------------------------------------------------------------------------* | Copyright (C) 2014 Christopher Kohlhaas | | | | This program is free software; you can redistribute it and/or modify | | it under the terms of the GNU General Public License as published by the | | Free Software Foundation. A copy of the license has been included with | | these distribution in the COPYING file, if not go to www.fsf.org | | | | As a special exception, you are granted the permissions to link this | | program with every library, which license fulfills the Open Source | | Definition as published by the Open Source Initiative (OSI). | *--------------------------------------------------------------------------*/ package org.rapla.storage.impl.server; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TimeZone; import java.util.TreeSet; import java.util.UUID; import java.util.concurrent.locks.Lock; import org.apache.commons.collections4.SortedBidiMap; import org.apache.commons.collections4.bidimap.DualTreeBidiMap; import org.rapla.RaplaResources; import org.rapla.components.util.Cancelable; import org.rapla.components.util.Command; import org.rapla.components.util.CommandScheduler; import org.rapla.components.util.DateTools; import org.rapla.components.util.SerializableDateTimeFormat; import org.rapla.components.util.TimeInterval; import org.rapla.components.util.Tools; import org.rapla.components.util.iterator.IterableChain; import org.rapla.components.xmlbundle.I18nBundle; import org.rapla.entities.Category; import org.rapla.entities.DependencyException; import org.rapla.entities.Entity; import org.rapla.entities.EntityNotFoundException; import org.rapla.entities.LastChangedTimestamp; import org.rapla.entities.MultiLanguageName; import org.rapla.entities.Named; import org.rapla.entities.Ownable; import org.rapla.entities.RaplaObject; import org.rapla.entities.RaplaType; import org.rapla.entities.Timestamp; import org.rapla.entities.UniqueKeyException; import org.rapla.entities.User; import org.rapla.entities.configuration.Preferences; import org.rapla.entities.configuration.internal.PreferencesImpl; import org.rapla.entities.domain.Allocatable; import org.rapla.entities.domain.Appointment; import org.rapla.entities.domain.AppointmentStartComparator; import org.rapla.entities.domain.EntityPermissionContainer; import org.rapla.entities.domain.Permission; import org.rapla.entities.domain.Permission.AccessLevel; import org.rapla.entities.domain.PermissionContainer; import org.rapla.entities.domain.PermissionContainer.Util; import org.rapla.entities.domain.RaplaObjectAnnotations; import org.rapla.entities.domain.Reservation; import org.rapla.entities.domain.ResourceAnnotations; import org.rapla.entities.domain.internal.AllocatableImpl; import org.rapla.entities.domain.internal.AppointmentImpl; import org.rapla.entities.domain.internal.ReservationImpl; import org.rapla.entities.dynamictype.Attribute; import org.rapla.entities.dynamictype.AttributeType; import org.rapla.entities.dynamictype.Classifiable; import org.rapla.entities.dynamictype.Classification; import org.rapla.entities.dynamictype.ClassificationFilter; import org.rapla.entities.dynamictype.DynamicType; import org.rapla.entities.dynamictype.DynamicTypeAnnotations; import org.rapla.entities.dynamictype.internal.AttributeImpl; import org.rapla.entities.dynamictype.internal.ClassificationImpl; import org.rapla.entities.dynamictype.internal.DynamicTypeImpl; import org.rapla.entities.internal.CategoryImpl; import org.rapla.entities.internal.ModifiableTimestamp; import org.rapla.entities.internal.UserImpl; import org.rapla.entities.storage.CannotExistWithoutTypeException; import org.rapla.entities.storage.DynamicTypeDependant; import org.rapla.entities.storage.EntityReferencer; import org.rapla.entities.storage.EntityReferencer.ReferenceInfo; import org.rapla.entities.storage.EntityResolver; import org.rapla.entities.storage.RefEntity; import org.rapla.entities.storage.internal.SimpleEntity; import org.rapla.facade.CalendarModel; import org.rapla.facade.Conflict; import org.rapla.facade.RaplaComponent; import org.rapla.framework.Disposable; import org.rapla.framework.RaplaException; import org.rapla.framework.RaplaLocale; import org.rapla.framework.logger.Logger; import org.rapla.gwtjsonrpc.common.AsyncCallback; import org.rapla.gwtjsonrpc.common.FutureResult; import org.rapla.gwtjsonrpc.common.ResultImpl; import org.rapla.server.internal.TimeZoneConverterImpl; import org.rapla.storage.CachableStorageOperator; import org.rapla.storage.CachableStorageOperatorCommand; import org.rapla.storage.IdCreator; import org.rapla.storage.LocalCache; import org.rapla.storage.PreferencePatch; import org.rapla.storage.RaplaNewVersionException; import org.rapla.storage.RaplaSecurityException; import org.rapla.storage.StorageOperator; import org.rapla.storage.UpdateEvent; import org.rapla.storage.UpdateOperation; import org.rapla.storage.UpdateResult; import org.rapla.storage.UpdateResult.Change; import org.rapla.storage.impl.AbstractCachableOperator; import org.rapla.storage.impl.EntityStore; public abstract class LocalAbstractCachableOperator extends AbstractCachableOperator implements Disposable, CachableStorageOperator, IdCreator { /** * set encryption if you want to enable password encryption. Possible values * are "sha" or "md5". */ private String encryption = "sha-1"; private ConflictFinder conflictFinder; private Map<String, SortedSet<Appointment>> appointmentMap; //private SortedSet<LastChangedTimestamp> timestampSet; private SortedBidiMap<String, DeleteUpdateEntry> deleteUpdateSet; private TimeZone systemTimeZone = TimeZone.getDefault(); private CommandScheduler scheduler; private Cancelable cleanConflictsTask; protected void addInternalTypes(LocalCache cache) throws RaplaException { { DynamicTypeImpl type = new DynamicTypeImpl(); String key = UNRESOLVED_RESOURCE_TYPE; type.setKey(key); type.setId(key); type.setAnnotation(DynamicTypeAnnotations.KEY_NAME_FORMAT, "{" + key + "}"); type.getName().setName("en", "anonymous"); type.setAnnotation(DynamicTypeAnnotations.KEY_CLASSIFICATION_TYPE, DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_PERSON); type.setResolver(this); { Permission newPermission = type.newPermission(); newPermission.setAccessLevel(Permission.READ_TYPE); type.addPermission(newPermission); } type.setReadOnly(); cache.put(type); } { DynamicTypeImpl type = new DynamicTypeImpl(); String key = ANONYMOUSEVENT_TYPE; type.setKey(key); type.setId(key); type.setAnnotation(DynamicTypeAnnotations.KEY_NAME_FORMAT, "{" + key + "}"); type.setAnnotation(DynamicTypeAnnotations.KEY_CLASSIFICATION_TYPE, DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RESERVATION); type.getName().setName("en", "anonymous"); type.setResolver(this); { Permission newPermission = type.newPermission(); newPermission.setAccessLevel(Permission.READ_TYPE); type.addPermission(newPermission); } type.setReadOnly(); cache.put(type); } { DynamicTypeImpl type = new DynamicTypeImpl(); String key = DEFAUTL_USER_TYPE; type.setKey(key); type.setId(key); type.setAnnotation(DynamicTypeAnnotations.KEY_CLASSIFICATION_TYPE, DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RAPLATYPE); //type.setAnnotation(DynamicTypeAnnotations.KEY_TRANSFERED_TO_CLIENT, DynamicTypeAnnotations.VALUE_TRANSFERED_TO_CLIENT_NEVER); addAttributeWithInternalId(type, "surname", AttributeType.STRING); addAttributeWithInternalId(type, "firstname", AttributeType.STRING); addAttributeWithInternalId(type, "email", AttributeType.STRING); { Permission newPermission = type.newPermission(); newPermission.setAccessLevel(Permission.READ_TYPE); type.addPermission(newPermission); } type.setResolver(this); type.setReadOnly(); cache.put(type); } { DynamicTypeImpl type = new DynamicTypeImpl(); String key = PERIOD_TYPE; type.setKey(key); type.setId(key); type.setAnnotation(DynamicTypeAnnotations.KEY_CLASSIFICATION_TYPE, DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RAPLATYPE); addAttributeWithInternalId(type, "name", AttributeType.STRING); addAttributeWithInternalId(type, "start", AttributeType.DATE); addAttributeWithInternalId(type, "end", AttributeType.DATE); type.setAnnotation(DynamicTypeAnnotations.KEY_NAME_FORMAT, "{name}"); type.setResolver(this); { Permission newPermission = type.newPermission(); newPermission.setAccessLevel(Permission.READ); type.addPermission(newPermission); } { Permission newPermission = type.newPermission(); newPermission.setAccessLevel(Permission.READ_TYPE); type.addPermission(newPermission); } type.setReadOnly(); cache.put(type); } { DynamicTypeImpl type = new DynamicTypeImpl(); String key = RAPLA_TEMPLATE; type.setKey(key); type.setId(key); type.setAnnotation(DynamicTypeAnnotations.KEY_CLASSIFICATION_TYPE, DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RAPLATYPE); addAttributeWithInternalId(type, "name", AttributeType.STRING); { Attribute att = addAttributeWithInternalId(type, "fixedtimeandduration", AttributeType.BOOLEAN); att.setDefaultValue(Boolean.TRUE); } type.setAnnotation(DynamicTypeAnnotations.KEY_NAME_FORMAT, "{name}"); type.setResolver(this); { Permission newPermission = type.newPermission(); newPermission.setAccessLevel(Permission.CREATE); type.addPermission(newPermission); } { Permission newPermission = type.newPermission(); newPermission.setAccessLevel(Permission.READ_TYPE); type.addPermission(newPermission); } type.setReadOnly(); cache.put(type); } { DynamicTypeImpl type = new DynamicTypeImpl(); String key = SYNCHRONIZATIONTASK_TYPE; type.setKey(key); type.setId(key); type.setAnnotation(DynamicTypeAnnotations.KEY_CLASSIFICATION_TYPE, DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RAPLATYPE); type.setAnnotation(DynamicTypeAnnotations.KEY_TRANSFERED_TO_CLIENT, DynamicTypeAnnotations.VALUE_TRANSFERED_TO_CLIENT_NEVER); addAttributeWithInternalId(type, "objectId", AttributeType.STRING); addAttributeWithInternalId(type, "externalObjectId", AttributeType.STRING); addAttributeWithInternalId(type, "status", AttributeType.STRING); addAttributeWithInternalId(type, "retries", AttributeType.STRING); addAttributeWithInternalId(type, "lastRetry", AttributeType.DATE); addAttributeWithInternalId(type, "lastError", AttributeType.STRING); type.setResolver(this); type.setReadOnly(); cache.put(type); } } protected void processPermissionGroups() throws RaplaException { Category userCategory = getSuperCategory().getCategory(Permission.GROUP_CATEGORY_KEY); if (userCategory != null) { User user = null; DynamicType t = getDynamicType(RAPLA_TEMPLATE); DynamicType templateType = (DynamicType) editObject(t, user); // TODO What does this method do? } } public LocalAbstractCachableOperator(Logger logger, RaplaResources i18n, RaplaLocale raplaLocale, CommandScheduler scheduler) { super(logger, i18n, raplaLocale); this.scheduler = scheduler; //context.lookupDeprecated( CommandScheduler.class); } @Override public FutureResult<User> connectAsync() { return new FutureResult<User>() { @Override public User get() throws Exception { return connect(null); } @Override public void get(final AsyncCallback<User> callback) { scheduler.schedule(new Command() { @Override public void execute() { User connect; try { connect = connect(null); } catch (RaplaException e) { callback.onFailure(e); return; } callback.onSuccess(connect); } }, 0); } }; } public void runWithReadLock(CachableStorageOperatorCommand cmd) throws RaplaException { Lock readLock = readLock(); try { cmd.execute(cache); } finally { unlock(readLock); } } /** * @param user the owner of the reservation or null for reservations from all users */ public FutureResult<Collection<Reservation>> getReservations(User user, Collection<Allocatable> allocatables, Date start, Date end, ClassificationFilter[] filters, Map<String, String> annotationQuery) { boolean excludeExceptions = false; HashSet<Reservation> reservationSet = new HashSet<Reservation>(); if (allocatables == null || allocatables.size() == 0) { allocatables = Collections.singleton(null); } try { for (Allocatable allocatable : allocatables) { Lock readLock = readLock(); SortedSet<Appointment> appointments; try { appointments = getAppointments(allocatable); } finally { unlock(readLock); } SortedSet<Appointment> appointmentSet = AppointmentImpl.getAppointments(appointments, user, start, end, excludeExceptions); for (Appointment appointment : appointmentSet) { Reservation reservation = appointment.getReservation(); if (!match(reservation, annotationQuery)) { continue; } // Ignore Templates if not explicitly requested // FIXME this special case should be refactored, so one can get all reservations in one method else if (RaplaComponent.isTemplate(reservation) && (annotationQuery == null || !annotationQuery.containsKey(RaplaObjectAnnotations.KEY_TEMPLATE))) { continue; } if (!reservationSet.contains(reservation)) { reservationSet.add(reservation); } } } ArrayList<Reservation> result = new ArrayList<Reservation>(reservationSet); removeFilteredClassifications(result, filters); return new ResultImpl<Collection<Reservation>>(result); } catch (RaplaException ex) { return new ResultImpl<Collection<Reservation>>(ex); } } public boolean match(Reservation reservation, Map<String, String> annotationQuery) { if (annotationQuery != null) { for (String key : annotationQuery.keySet()) { String annotationParam = annotationQuery.get(key); String annotation = reservation.getAnnotation(key); if (annotation == null || annotationParam == null) { if (annotationParam != null) { return false; } } else { if (!annotation.equals(annotationParam)) { return false; } } } } return true; } public Collection<String> getTemplateNames() throws RaplaException { Lock readLock = readLock(); Collection<? extends Reservation> reservations; try { reservations = cache.getReservations(); } finally { unlock(readLock); } //Reservation[] reservations = cache.getReservations(user, start, end, filters.toArray(ClassificationFilter.CLASSIFICATIONFILTER_ARRAY)); Set<String> templates = new LinkedHashSet<String>(); for (Reservation r : reservations) { String templateName = r.getAnnotation(RaplaObjectAnnotations.KEY_TEMPLATE); if (templateName != null) { templates.add(templateName); } } return templates; } @Override public String createId(RaplaType raplaType) throws RaplaException { String string = UUID.randomUUID().toString(); String result = replaceFirst(raplaType, string); return result; } private String replaceFirst(RaplaType raplaType, String string) { Character firstLetter = raplaType.getFirstLetter(); String result = firstLetter + string.substring(1); return result; } public String createId(RaplaType raplaType, String seed) throws RaplaException { byte[] data = new byte[16]; MessageDigest md; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RaplaException(e.getMessage(), e); } data = md.digest(seed.getBytes()); if (data.length != 16) { throw new RaplaException("Wrong algorithm"); } data[6] &= 0x0f; /* clear version */ data[6] |= 0x40; /* set to version 4 */ data[8] &= 0x3f; /* clear variant */ data[8] |= 0x80; /* set to IETF variant */ long msb = 0; long lsb = 0; for (int i = 0; i < 8; i++) msb = (msb << 8) | (data[i] & 0xff); for (int i = 8; i < 16; i++) lsb = (lsb << 8) | (data[i] & 0xff); long mostSigBits = msb; long leastSigBits = lsb; UUID uuid = new UUID(mostSigBits, leastSigBits); String result = replaceFirst(raplaType, uuid.toString()); return result; } public String[] createIdentifier(RaplaType raplaType, int count) throws RaplaException { String[] ids = new String[count]; for (int i = 0; i < count; i++) { ids[i] = createId(raplaType); } return ids; } public Date today() { long time = getCurrentTimestamp().getTime(); long offset = TimeZoneConverterImpl.getOffset(DateTools.getTimeZone(), systemTimeZone, time); Date raplaTime = new Date(time + offset); return DateTools.cutDate(raplaTime); } public Date getCurrentTimestamp() { long time = System.currentTimeMillis(); return new Date(time); } public void setTimeZone(TimeZone timeZone) { systemTimeZone = timeZone; } public TimeZone getTimeZone() { return systemTimeZone; } public String authenticate(String username, String password) throws RaplaException { Lock readLock = readLock(); try { getLogger().debug("Check password for User " + username); User user = cache.getUser(username); if (user != null) { String userId = user.getId(); if (checkPassword(userId, password)) { return userId; } } getLogger().warn("Login failed for " + username); throw new RaplaSecurityException(i18n.getString("error.login")); } finally { unlock(readLock); } } public boolean canChangePassword() throws RaplaException { return true; } public void changePassword(User user, char[] oldPassword, char[] newPassword) throws RaplaException { getLogger().info("Change password for User " + user.getUsername()); String userId = user.getId(); String password = new String(newPassword); if (encryption != null) password = encrypt(encryption, password); Lock writeLock = writeLock(); try { cache.putPassword(userId, password); } finally { unlock(writeLock); } User editObject = editObject(user, null); List<Entity> editList = new ArrayList<Entity>(1); editList.add(editObject); Collection<Entity> removeList = Collections.emptyList(); // synchronization will be done in the dispatch method storeAndRemove(editList, removeList, user); } public void changeName(User user, String title, String firstname, String surname) throws RaplaException { User editableUser = editObject(user, user); Allocatable personReference = editableUser.getPerson(); if (personReference == null) { editableUser.setName(surname); storeUser(editableUser); } else { Allocatable editablePerson = editObject(personReference, null); Classification classification = editablePerson.getClassification(); { Attribute attribute = classification.getAttribute("title"); if (attribute != null) { classification.setValue(attribute, title); } } { Attribute attribute = classification.getAttribute("firstname"); if (attribute != null) { classification.setValue(attribute, firstname); } } { Attribute attribute = classification.getAttribute("surname"); if (attribute != null) { classification.setValue(attribute, surname); } } ArrayList<Entity> arrayList = new ArrayList<Entity>(); arrayList.add(editableUser); arrayList.add(editablePerson); Collection<Entity> storeObjects = arrayList; Collection<Entity> removeObjects = Collections.emptySet(); // synchronization will be done in the dispatch method storeAndRemove(storeObjects, removeObjects, null); } } public void changeEmail(User user, String newEmail) throws RaplaException { User editableUser = user.isReadOnly() ? editObject(user, (User) user) : user; Allocatable personReference = editableUser.getPerson(); ArrayList<Entity> arrayList = new ArrayList<Entity>(); Collection<Entity> storeObjects = arrayList; Collection<Entity> removeObjects = Collections.emptySet(); storeObjects.add(editableUser); if (personReference == null) { editableUser.setEmail(newEmail); } else { Allocatable editablePerson = editObject(personReference, null); Classification classification = editablePerson.getClassification(); classification.setValue("email", newEmail); storeObjects.add(editablePerson); } storeAndRemove(storeObjects, removeObjects, null); } protected void resolveInitial(Collection<? extends Entity> entities, EntityResolver resolver) throws RaplaException { testResolve(entities); for (Entity entity : entities) { if (entity instanceof EntityReferencer) { ((EntityReferencer) entity).setResolver(resolver); } } processUserPersonLink(entities); } protected Collection<Entity> migrateTemplates() throws RaplaException { Collection<Allocatable> allocatables = cache.getAllocatables(); for (Allocatable r : allocatables) { String key = r.getClassification().getType().getKey(); if (key.equals(StorageOperator.RAPLA_TEMPLATE)) { return Collections.emptyList(); } } Map<String, Collection<Reservation>> templateMap = new HashMap<String, Collection<Reservation>>(); for (Reservation r : cache.getReservations()) { String annotation = r.getAnnotation(RaplaObjectAnnotations.KEY_TEMPLATE, null); if (annotation == null) { continue; } Collection<Reservation> collection = templateMap.get(annotation); if (collection == null) { collection = new ArrayList<Reservation>(); templateMap.put(annotation, collection); } collection.add(r); } if (templateMap.size() == 0) { return Collections.emptyList(); } getLogger().warn("Found old templates. Migrating."); Collection<Entity> toStore = new HashSet<Entity>(); for (String templateKey : templateMap.keySet()) { Collection<Reservation> templateEvents = templateMap.get(templateKey); Date date = getCurrentTimestamp(); AllocatableImpl template = new AllocatableImpl(date, date); template.setResolver(this); String templateId = createId(Allocatable.TYPE); Classification newClassification = getDynamicType(RAPLA_TEMPLATE).newClassification(); newClassification.setValue("name", templateKey); template.setClassification(newClassification); template.setId(templateId); Permission permission = template.newPermission(); permission.setAccessLevel(AccessLevel.READ); template.addPermission(permission); User owner = null; for (Reservation r : templateEvents) { r.setAnnotation(RaplaObjectAnnotations.KEY_TEMPLATE, templateId); toStore.add(r); if (owner == null) { owner = r.getOwner(); } } getLogger().info("Migrating " + templateKey); template.setOwner(owner); toStore.add(template); } return toStore; } protected void processUserPersonLink(Collection<? extends Entity> entities) throws RaplaException { // resolve emails Map<String, Allocatable> resolvingMap = new HashMap<String, Allocatable>(); for (Entity entity : entities) { if (entity instanceof Allocatable) { Allocatable allocatable = (Allocatable) entity; final Classification classification = allocatable.getClassification(); final Attribute attribute = classification.getAttribute("email"); if (attribute != null) { final String email = (String) classification.getValue(attribute); if (email != null) { resolvingMap.put(email, allocatable); } } } } for (Entity entity : entities) { if (entity.getRaplaType().getTypeClass() == User.class) { User user = (User) entity; String email = user.getEmail(); if (email != null && email.trim().length() > 0) { Allocatable person = resolvingMap.get(email); if (person != null) { user.setPerson(person); } } } } } public void confirmEmail(User user, String newEmail) throws RaplaException { throw new RaplaException("Email confirmation must be done in the remotestorage class"); } public Collection<Conflict> getConflicts(User user) throws RaplaException { Lock readLock = readLock(); try { return conflictFinder.getConflicts(user); } finally { unlock(readLock); } } boolean disposing; public void dispose() { // prevent reentrance in dispose synchronized (this) { if (disposing) { getLogger().warn("Disposing is called twice", new RaplaException("")); return; } disposing = true; } try { if (cleanConflictsTask != null) { cleanConflictsTask.cancel(); } forceDisconnect(); } finally { disposing = false; } } protected void forceDisconnect() { try { disconnect(); } catch (Exception ex) { getLogger().error("Error during disconnect ", ex); } } /** performs Integrity constraints check */ protected void check(final UpdateEvent evt, final EntityStore store) throws RaplaException { Set<Entity> storeObjects = new HashSet<Entity>(evt.getStoreObjects()); //Set<Entity> removeObjects = new HashSet<Entity>(evt.getRemoveObjects()); setResolverAndCheckReferences(evt, store); checkConsistency(evt, store); checkUnique(evt, store); checkNoDependencies(evt, store); checkVersions(storeObjects); } protected void updateLastChanged(UpdateEvent evt) throws RaplaException { Date currentTime = getCurrentTimestamp(); String userId = evt.getUserId(); User lastChangedBy = (userId != null) ? resolve(userId, User.class) : null; for (Entity e : evt.getStoreObjects()) { if (e instanceof ModifiableTimestamp) { ModifiableTimestamp modifiableTimestamp = (ModifiableTimestamp) e; Date lastChangeTime = modifiableTimestamp.getLastChanged(); if (lastChangeTime != null && lastChangeTime.equals(currentTime)) { // wait 1 ms to increase timestamp try { Thread.sleep(1); } catch (InterruptedException e1) { throw new RaplaException(e1.getMessage(), e1); } currentTime = getCurrentTimestamp(); } modifiableTimestamp.setLastChanged(currentTime); modifiableTimestamp.setLastChangedBy(lastChangedBy); } } for (PreferencePatch patch : evt.getPreferencePatches()) { patch.setLastChanged(currentTime); } } class TimestampComparator implements Comparator<LastChangedTimestamp> { public int compare(LastChangedTimestamp o1, LastChangedTimestamp o2) { if (o1 == o2) { return 0; } Date d1 = o1.getLastChanged(); Date d2 = o2.getLastChanged(); // if d1 is null and d2 is not then d1 is before d2 if (d1 == null && d2 != null) { return -1; } // if d2 is null and d1 is not then d2 is before d1 if (d1 != null && d2 == null) { return 1; } if (d1 != null && d2 != null) { int result = d1.compareTo(d2); if (result != 0) { return result; } } String id1 = (o1 instanceof Entity) ? ((Entity) o1).getId() : o1.toString(); String id2 = (o2 instanceof Entity) ? ((Entity) o2).getId() : o2.toString(); if (id1 == null) { if (id2 == null) { throw new IllegalStateException("Can't compare two entities without ids"); } else { return -1; } } else if (id2 == null) { return 1; } return id1.compareTo(id2); } } protected void initIndizes() { deleteUpdateSet = new DualTreeBidiMap<String, DeleteUpdateEntry>(); timestampSetAddAll(cache.getDynamicTypes()); timestampSetAddAll(cache.getReservations()); timestampSetAddAll(cache.getAllocatables()); timestampSetAddAll(cache.getUsers()); // The appointment map appointmentMap = new HashMap<String, SortedSet<Appointment>>(); for (Reservation r : cache.getReservations()) { for (Appointment app : ((ReservationImpl) r).getAppointmentList()) { Reservation reservation = app.getReservation(); Allocatable[] allocatables = reservation.getAllocatablesFor(app); { Collection<Appointment> list = getAndCreateList(appointmentMap, null); list.add(app); } for (Allocatable alloc : allocatables) { Collection<Appointment> list = getAndCreateList(appointmentMap, alloc); list.add(app); } } } Date today2 = today(); AllocationMap allocationMap = new AllocationMap() { public SortedSet<Appointment> getAppointments(Allocatable allocatable) { return LocalAbstractCachableOperator.this.getAppointments(allocatable); } @SuppressWarnings("unchecked") public Collection<Allocatable> getAllocatables() { return (Collection) cache.getAllocatables(); } }; // The conflict map Logger logger = getLogger(); conflictFinder = new ConflictFinder(allocationMap, today2, logger, this, cache); long delay = 0;//DateTools.MILLISECONDS_PER_HOUR; long period = DateTools.MILLISECONDS_PER_HOUR; Command cleanUpConflicts = new Command() { @Override public void execute() throws Exception { removeOldConflicts(); } }; cleanConflictsTask = scheduler.schedule(cleanUpConflicts, delay, period); } private void timestampSetAddAll(Collection<? extends Entity> entities) { for (Entity entity : entities) { boolean isDelete = false; addToDeleteUpdate(entity, isDelete); } } /** updates the bindings of the resources and returns a map with all processed allocation changes*/ private void updateIndizes(UpdateResult result) { Map<Allocatable, AllocationChange> toUpdate = new HashMap<Allocatable, AllocationChange>(); List<Allocatable> removedAllocatables = new ArrayList<Allocatable>(); for (UpdateOperation operation : result.getOperations()) { Entity current = operation.getCurrent(); RaplaType raplaType = current.getRaplaType(); if (raplaType == Reservation.TYPE) { if (operation instanceof UpdateResult.Remove) { Reservation old = (Reservation) current; for (Appointment app : old.getAppointments()) { updateBindings(toUpdate, old, app, true); } } if (operation instanceof UpdateResult.Add) { Reservation newReservation = (Reservation) ((UpdateResult.Add) operation).getNew(); for (Appointment app : newReservation.getAppointments()) { updateBindings(toUpdate, newReservation, app, false); } } if (operation instanceof UpdateResult.Change) { Reservation oldReservation = (Reservation) ((UpdateResult.Change) operation).getOld(); Reservation newReservation = (Reservation) ((UpdateResult.Change) operation).getNew(); Appointment[] oldAppointments = oldReservation.getAppointments(); for (Appointment oldApp : oldAppointments) { updateBindings(toUpdate, oldReservation, oldApp, true); } Appointment[] newAppointments = newReservation.getAppointments(); for (Appointment newApp : newAppointments) { updateBindings(toUpdate, newReservation, newApp, false); } } } if (raplaType == DynamicType.TYPE) { if (operation instanceof UpdateResult.Change) { DynamicType dynamicType = (DynamicType) current; DynamicType old = (DynamicType) ((UpdateResult.Change) operation).getOld(); String conflictsNew = dynamicType.getAnnotation(DynamicTypeAnnotations.KEY_CONFLICTS); String conflictsOld = old.getAnnotation(DynamicTypeAnnotations.KEY_CONFLICTS); if (conflictsNew != conflictsOld) { if (conflictsNew == null || conflictsOld == null || !conflictsNew.equals(conflictsOld)) { Collection<Reservation> reservations = cache.getReservations(); for (Reservation reservation : reservations) { if (dynamicType.equals(reservation.getClassification().getType())) { Collection<AppointmentImpl> appointments = ((ReservationImpl) reservation) .getAppointmentList(); for (Appointment app : appointments) { updateBindings(toUpdate, reservation, app, true); } for (Appointment app : appointments) { updateBindings(toUpdate, reservation, app, false); } } } } } } } if (raplaType == Allocatable.TYPE) { if (operation instanceof UpdateResult.Remove) { Allocatable old = (Allocatable) current; removedAllocatables.add(old); } } if (raplaType == Conflict.TYPE) { Conflict conflict = (Conflict) current; String conflictId = conflict.getId(); Date date = getCurrentTimestamp(); boolean appointment1Enabled = conflict.isAppointment1Enabled(); boolean appointment2Enabled = conflict.isAppointment2Enabled(); conflictFinder.setConflictEnabledState(conflictId, date, appointment1Enabled, appointment2Enabled); } } for (Allocatable alloc : removedAllocatables) { SortedSet<Appointment> sortedSet = appointmentMap.get(alloc); if (sortedSet != null && !sortedSet.isEmpty()) { getLogger().error( "Removing non empty appointment map for resource " + alloc + " Appointments:" + sortedSet); } appointmentMap.remove(alloc); } Date today = today(); // processes the conflicts and adds the changes to the result conflictFinder.updateConflicts(toUpdate, result, today, removedAllocatables); checkAbandonedAppointments(); for (UpdateOperation operation : result.getOperations()) { Entity current = operation.getCurrent(); RaplaType raplaType = current.getRaplaType(); if (raplaType == Conflict.TYPE || raplaType == Allocatable.TYPE || raplaType == Reservation.TYPE || raplaType == DynamicType.TYPE || raplaType == User.TYPE || raplaType == Preferences.TYPE || raplaType == Category.TYPE) { if (operation instanceof UpdateResult.Remove) { //LastChangedTimestamp old = (LastChangedTimestamp) current; //timestampSet.remove( old); addToDeleteUpdate(current, true); } if (operation instanceof UpdateResult.Add) { Entity newEntity = ((UpdateResult.Add) operation).getNew(); addToDeleteUpdate(newEntity, false); } if (operation instanceof UpdateResult.Change) { Entity newEntity = ((UpdateResult.Change) operation).getNew(); //LastChangedTimestamp oldEntity = (LastChangedTimestamp) ((UpdateResult.Change) operation).getOld(); addToDeleteUpdate(newEntity, false); } } } // Order is important. Can't remove from database if removed from cache first removeConflictsFromDatabase(getConflictsToDeleteFromDatabase(result)); removeConflictsFromCache(getConflictsToDeleteFromCache(result)); // Collection<Change> changes = result.getOperations( UpdateResult.Change.class); // for ( Change change:changes) // { // Entity old = change.getOld(); // if (!( old instanceof Conflict)) // { // continue; // } // Conflict conflict = (Conflict)change.getNew(); // if (conflict.isEnabledAppointment1() && conflict.isEnabledAppointment2()) // { // conflicts.add( conflict); // } // } } private void addToDeleteUpdate(Entity current, boolean isDelete) { Class typeClass = current.getRaplaType().getTypeClass(); String id = current.getId(); @SuppressWarnings("unchecked") Class<? extends Entity> type = (Class<? extends Entity>) typeClass; DeleteUpdateEntry entry = deleteUpdateSet.get(id); if (entry == null) { entry = new DeleteUpdateEntry(); } else { DeleteUpdateEntry remove = deleteUpdateSet.remove(id); if (isDelete && remove == null) { getLogger().warn("Can't remove entry for id " + id); } } if (!isDelete && current instanceof LastChangedTimestamp) { entry.timestamp = ((LastChangedTimestamp) current).getLastChanged(); } else { entry.timestamp = getCurrentTimestamp(); } ReferenceInfo ref = new ReferenceInfo(id, type); entry.isDelete = isDelete; entry.reference = ref; if (current instanceof EntityPermissionContainer) { entry.addPermissions((EntityPermissionContainer) current, Permission.READ_NO_ALLOCATION); } else if (current instanceof Conflict) { if (isDelete) { entry.affectAll = true; } else { Conflict conflict = (Conflict) current; addPermissions(entry, conflict.getReservation1()); addPermissions(entry, conflict.getReservation2()); } } else if (current instanceof Preferences) { String owner = ((PreferencesImpl) current).getOwnerId(); if (owner != null) { entry.addUserIds(Collections.singletonList(owner)); } } deleteUpdateSet.put(entry.getId(), entry); } private void addPermissions(DeleteUpdateEntry entry, String reservation1) { Reservation event = tryResolve(reservation1, Reservation.class); if (event != null) { entry.addPermissions(event, Permission.EDIT); } else { DeleteUpdateEntry deleteUpdateEntry = deleteUpdateSet.get(event); if (deleteUpdateEntry != null) { entry.addPermssions(deleteUpdateEntry); } } } @Override public Collection<ReferenceInfo> getDeletedEntities(User user, final Date timestamp) throws RaplaException { boolean isDelete = true; Collection<ReferenceInfo> result = getEntities(user, timestamp, isDelete); return result; } private boolean isAffected(DeleteUpdateEntry entry, User user) { if (entry.affectAll || user.isAdmin()) { return true; } else { if (entry.affectedGroupIds != null) { for (String id : entry.affectedGroupIds) { Category group = tryResolve(id, Category.class); if (group != null) { if (user.belongsTo(group)) { return true; } } } } if (entry.affectedUserIds != null) { String userId = user.getId(); for (String id : entry.affectedUserIds) { if (id.equals(userId)) { return true; } } } } return false; } class DeleteUpdateEntry implements Comparable<DeleteUpdateEntry> { public boolean affectAll; Date timestamp; ReferenceInfo reference; Set<String> affectedGroupIds; Set<String> affectedUserIds; boolean isDelete; @Override public int compareTo(DeleteUpdateEntry o) { if (o == this) { return 0; } Date time1 = this.timestamp; Date time2 = o.timestamp; int result = time1.compareTo(time2); if (result != 0) { return result; } String deleteId = getId(); result = deleteId.compareTo(o.getId()); return result; } public void addPermssions(DeleteUpdateEntry deleteUpdateEntry) { if (deleteUpdateEntry.affectAll) { affectAll = true; } Set<String> groupIds = deleteUpdateEntry.affectedGroupIds; if (groupIds != null) { addGroupIds(groupIds); } Set<String> userIds = deleteUpdateEntry.affectedUserIds; if (userIds != null) { addUserIds(userIds); } } public void addUserIds(Collection<String> userIds) { if (affectedUserIds == null) { affectedUserIds = new HashSet<String>(1); } affectedUserIds.addAll(userIds); } public void addGroupIds(Collection<String> groupIds) { if (affectedGroupIds == null) { affectedGroupIds = new HashSet<String>(1); } affectedGroupIds.addAll(groupIds); } String getId() { return reference.getId(); } @Override public boolean equals(Object o) { boolean equals = getId().equals(((DeleteUpdateEntry) o).getId()); return equals; } @Override public int hashCode() { return getId().hashCode(); } @Override public String toString() { return reference + (isDelete ? " removed on " : "changed on ") + timestamp; } private void addPermissions(EntityPermissionContainer current, Permission.AccessLevel minimumLevel) { DeleteUpdateEntry entry = this; if (current instanceof Ownable) { String ownerId = ((Ownable) current).getOwnerId(); if (ownerId != null) { if (entry.affectedUserIds == null) { entry.affectedUserIds = new HashSet<String>(1); } entry.affectedUserIds.add(ownerId); } } Collection<Permission> permissions = current.getPermissionList(); for (Permission p : permissions) { Category group = p.getGroup(); User user = p.getUser(); Permission.AccessLevel accessLevel = p.getAccessLevel(); if (minimumLevel.includes(accessLevel)) { continue; } if (group != null) { if (entry.affectedGroupIds == null) { entry.affectedGroupIds = new HashSet<String>(1); } entry.affectedGroupIds.add(group.getId()); } else if (user != null) { if (entry.affectedUserIds == null) { entry.affectedUserIds = new HashSet<String>(1); } entry.affectedUserIds.add(user.getId()); } else { // we have an all usere group here entry.affectAll = true; break; } } } } @Override public Collection<Entity> getUpdatedEntities(final User user, final Date timestamp) throws RaplaException { boolean isDelete = false; Collection<ReferenceInfo> references = getEntities(user, timestamp, isDelete); ArrayList<Entity> result = new ArrayList<Entity>(); for (ReferenceInfo info : references) { String id = info.getId(); Class<? extends Entity> entityClass = info.getType(); if (entityClass != null && entityClass == Conflict.class) { Conflict conflict = conflictFinder.findConflict(id, timestamp); if (conflict != null) { result.add(conflict); } } else { Entity entity = tryResolve(id, entityClass); if (entity != null) { result.add(entity); } else { getLogger().warn("Can't find updated entity for id " + id); } } } return result; } private Collection<ReferenceInfo> getEntities(User user, final Date timestamp, boolean isDelete) throws RaplaException { DeleteUpdateEntry fromElement = new DeleteUpdateEntry(); Class<? extends Entity> type = Allocatable.class; String dummyId = ""; fromElement.reference = new ReferenceInfo(dummyId, type); fromElement.timestamp = timestamp; LinkedList<ReferenceInfo> result = new LinkedList<ReferenceInfo>(); Lock lock = readLock(); try { SortedMap<DeleteUpdateEntry, String> tailMap = deleteUpdateSet.inverseBidiMap().tailMap(fromElement); Set<DeleteUpdateEntry> tailSet = tailMap.keySet(); for (DeleteUpdateEntry entry : tailSet) { if (entry.isDelete != isDelete) { continue; } if (isAffected(entry, user)) { ReferenceInfo deletedReference = entry.reference; result.add(deletedReference); } } } finally { unlock(lock); } return result; } protected void updateBindings(Map<Allocatable, AllocationChange> toUpdate, Reservation reservation, Appointment app, boolean remove) { Set<Allocatable> allocatablesToProcess = new HashSet<Allocatable>(); allocatablesToProcess.add(null); if (reservation != null) { Allocatable[] allocatablesFor = reservation.getAllocatablesFor(app); allocatablesToProcess.addAll(Arrays.asList(allocatablesFor)); // This double check is very imperformant and will be removed in the future, if it doesnt show in test runs // if ( remove) // { // Collection<Allocatable> allocatables = cache.getCollection(Allocatable.class); // for ( Allocatable allocatable:allocatables) // { // SortedSet<Appointment> appointmentSet = this.appointmentMap.get( allocatable.getId()); // if ( appointmentSet == null) // { // continue; // } // for (Appointment app1:appointmentSet) // { // if ( app1.equals( app)) // { // if ( !allocatablesToProcess.contains( allocatable)) // { // getLogger().error("Old reservation " + reservation.toString() + " has not the correct allocatable information. Using full search for appointment " + app + " and resource " + allocatable ) ; // allocatablesToProcess.add(allocatable); // } // } // } // } // } } else { getLogger().error("Appointment without reservation found " + app + " ignoring."); } for (Allocatable allocatable : allocatablesToProcess) { AllocationChange updateSet; if (allocatable != null) { updateSet = toUpdate.get(allocatable); if (updateSet == null) { updateSet = new AllocationChange(); toUpdate.put(allocatable, updateSet); } } else { updateSet = null; } if (remove) { Collection<Appointment> appointmentSet = getAndCreateList(appointmentMap, allocatable); // binary search could fail if the appointment has changed since the last add, which should not // happen as we only put and search immutable objects in the map. But the method is left here as a failsafe // with a log messaget if (!appointmentSet.remove(app)) { getLogger().error( "Appointent has changed, so its not found in indexed binding map. Removing via full search"); // so we need to traverse all appointment Iterator<Appointment> it = appointmentSet.iterator(); while (it.hasNext()) { if (app.equals(it.next())) { it.remove(); break; } } } if (updateSet != null) { updateSet.toRemove.add(app); } } else { SortedSet<Appointment> appointmentSet = getAndCreateList(appointmentMap, allocatable); appointmentSet.add(app); if (updateSet != null) { updateSet.toChange.add(app); } } } } static final SortedSet<Appointment> EMPTY_SORTED_SET = Collections .unmodifiableSortedSet(new TreeSet<Appointment>()); protected SortedSet<Appointment> getAppointments(Allocatable allocatable) { String allocatableId = allocatable != null ? allocatable.getId() : null; SortedSet<Appointment> s = appointmentMap.get(allocatableId); if (s == null) { return EMPTY_SORTED_SET; } return Collections.unmodifiableSortedSet(s); } private SortedSet<Appointment> getAndCreateList(Map<String, SortedSet<Appointment>> appointmentMap, Allocatable alloc) { String allocationId = alloc != null ? alloc.getId() : null; SortedSet<Appointment> set = appointmentMap.get(allocationId); if (set == null) { set = new TreeSet<Appointment>(new AppointmentStartComparator()); appointmentMap.put(allocationId, set); } return set; } @Override protected UpdateResult update(UpdateEvent evt) throws RaplaException { UpdateResult update = super.update(evt); updateIndizes(update); processPermissionGroups(); return update; } private void removeOldConflicts() throws RaplaException { Map<Entity, Entity> oldEntities = new LinkedHashMap<Entity, Entity>(); Collection<Entity> updatedEntities = new LinkedHashSet<Entity>(); Collection<Entity> toRemove = new LinkedHashSet<Entity>(); TimeInterval invalidateInterval = null; String userId = null; UpdateResult result = createUpdateResult(oldEntities, updatedEntities, toRemove, invalidateInterval, userId); //Date today = getCurrentTimestamp(); Date today = today(); Set<Conflict> conflictsToDelete; { Lock readLock = readLock(); try { @SuppressWarnings("unused") int count = conflictFinder.removeOldConflicts(result, today); conflictsToDelete = getConflictsToDeleteFromCache(result); } finally { unlock(readLock); } } Collection<Conflict> conflicts = cache.getConflicts(); for (Conflict conflict : conflicts) { if (!conflictFinder.isActiveConflict(conflict, today)) { conflictsToDelete.add(conflict); } } if (conflictsToDelete.size() > 0) { getLogger().info("Removing old conflicts " + conflictsToDelete.size()); Lock writeLock = writeLock(); try { //Order is important they can't be removed from database if they are not in cache removeConflictsFromDatabase(conflictsToDelete); removeConflictsFromCache(conflictsToDelete); } finally { unlock(writeLock); } } fireStorageUpdated(result); } protected void removeConflictsFromCache(Collection<Conflict> disabledConflicts) { for (Conflict conflict : disabledConflicts) { cache.remove(conflict); } } protected void removeConflictsFromDatabase(@SuppressWarnings("unused") Collection<Conflict> disabledConflicts) { } private Set<Conflict> getConflictsToDeleteFromCache(UpdateResult result) { Set<Entity> removed = result.getRemoved(); Set<Conflict> conflicts = new HashSet<Conflict>(); for (Entity entity : removed) { if (entity instanceof Conflict) { if (cache.getConflictIds().contains(entity.getId())) { conflicts.add((Conflict) entity); } } } return conflicts; } private List<Conflict> getConflictsToDeleteFromDatabase(UpdateResult result) { Set<Entity> removed = result.getRemoved(); List<Conflict> conflicts = new ArrayList<Conflict>(); for (Entity entity : removed) { if (entity instanceof Conflict) { if (cache.getConflictIds().contains(entity.getId())) { conflicts.add((Conflict) entity); } } } Collection<Change> changes = result.getOperations(UpdateResult.Change.class); for (Change change : changes) { Entity old = change.getOld(); if (!(old instanceof Conflict)) { continue; } Conflict conflict = (Conflict) change.getNew(); if (conflict.isAppointment1Enabled() && conflict.isAppointment2Enabled()) { conflicts.add(conflict); } } return conflicts; } protected void preprocessEventStorage(final UpdateEvent evt) throws RaplaException { EntityStore store = new EntityStore(this, this.getSuperCategory()); Collection<Entity> storeObjects = evt.getStoreObjects(); store.addAll(storeObjects); for (Entity entity : storeObjects) { if (getLogger().isDebugEnabled()) getLogger().debug("Contextualizing " + entity); ((EntityReferencer) entity).setResolver(store); // add all child categories to store if (entity instanceof Category) { Set<Category> children = getAllCategories((Category) entity); store.addAll(children); } } // Collection<Entity>removeObjects = evt.getRemoveIds(); // store.addAll( removeObjects ); // // for ( Entity entity:removeObjects) // { // ((EntityReferencer)entity).setResolver( store); // } // add transitve changes to event addClosure(evt, store); // check event for inconsistencies check(evt, store); // update last changed date updateLastChanged(evt); } /** * Create a closure for all objects that should be updated. The closure * contains all objects that are sub-entities of the entities and all * objects and all other objects that are affected by the update: e.g. * Classifiables when the DynamicType changes. The method will recursivly * proceed with all discovered objects. */ protected void addClosure(final UpdateEvent evt, EntityStore store) throws RaplaException { Collection<Entity> storeObjects = new ArrayList<Entity>(evt.getStoreObjects()); Collection<String> removeIds = new ArrayList<String>(evt.getRemoveIds()); for (Entity entity : storeObjects) { evt.putStore(entity); RaplaType raplaType = entity.getRaplaType(); if (DynamicType.TYPE == raplaType) { DynamicTypeImpl dynamicType = (DynamicTypeImpl) entity; addChangedDynamicTypeDependant(evt, store, dynamicType, false); } if (entity instanceof Classifiable) { processOldPermssionModify(store, entity); } } for (Entity entity : storeObjects) { // update old classifiables, that may not been update before via a change event // that could be the case if an old reservation is restored via undo but the dynamic type changed in between. // The undo cache does not notice the change in type if (entity instanceof Classifiable && entity instanceof Timestamp) { Date lastChanged = ((LastChangedTimestamp) entity).getLastChanged(); ClassificationImpl classification = (ClassificationImpl) ((Classifiable) entity) .getClassification(); DynamicTypeImpl dynamicType = classification.getType(); Date typeLastChanged = dynamicType.getLastChanged(); if (typeLastChanged != null && lastChanged != null && typeLastChanged.after(lastChanged)) { if (classification.needsChange(dynamicType)) { addChangedDependencies(evt, store, dynamicType, entity, false); } } } // TODO add conversion of classification filters or other dynamictypedependent that are stored in preferences // for (PreferencePatch patch:evt.getPreferencePatches()) // { // for (String key: patch.keySet()) // { // Object object = patch.get( key); // if ( object instanceof DynamicTypeDependant) // { // // } // } // } } for (String removeId : removeIds) { Entity entity = store.tryResolve(removeId); if (entity == null) { continue; } if (DynamicType.TYPE == entity.getRaplaType()) { DynamicTypeImpl dynamicType = (DynamicTypeImpl) entity; addChangedDynamicTypeDependant(evt, store, dynamicType, true); } // If entity is a user, remove the preference object if (User.TYPE == entity.getRaplaType()) { addRemovedUserDependant(evt, store, (User) entity); } } Set<Entity> deletedCategories = getDeletedCategories(storeObjects); for (Entity entity : deletedCategories) { evt.putRemove(entity); } } @SuppressWarnings("deprecation") private void processOldPermssionModify(@SuppressWarnings("unused") EntityStore store, Entity entity) { Class<? extends Entity> clazz = (entity instanceof Reservation) ? Reservation.class : Allocatable.class; Classifiable persistant = (Classifiable) tryResolve(((Entity) entity).getId(), clazz); Util.processOldPermissionModify((Classifiable) entity, persistant); } // protected void setCache(final LocalCache cache) { // super.setCache( cache); // if ( idTable == null) // { // idTable = new IdTable(); // } // idTable.setCache(cache); // } // protected void addChangedDynamicTypeDependant(UpdateEvent evt, EntityStore store, DynamicTypeImpl type, boolean toRemove) throws RaplaException { List<Entity> referencingEntities = getReferencingEntities(type, store); Iterator<Entity> it = referencingEntities.iterator(); while (it.hasNext()) { Entity entity = it.next(); if (!(entity instanceof DynamicTypeDependant)) { continue; } DynamicTypeDependant dependant = (DynamicTypeDependant) entity; // Classifiables need update? if (!dependant.needsChange(type) && !toRemove) continue; if (getLogger().isDebugEnabled()) getLogger().debug("Classifiable " + entity + " needs change!"); // Classifiables are allready on the store list addChangedDependencies(evt, store, type, entity, toRemove); } } private void addChangedDependencies(UpdateEvent evt, EntityStore store, DynamicTypeImpl type, Entity entity, boolean toRemove) throws EntityNotFoundException, RaplaException { DynamicTypeDependant dependant; if (evt.getStoreObjects().contains(entity)) { dependant = (DynamicTypeDependant) evt.findEntity(entity); } else { // no, then create a clone of the classfiable object and add to list User user = null; if (evt.getUserId() != null) { user = resolve(cache, evt.getUserId(), User.class); } @SuppressWarnings("unchecked") Class<Entity> entityType = entity.getRaplaType().getTypeClass(); Entity persistant = store.tryResolve(entity.getId(), entityType); dependant = (DynamicTypeDependant) editObject(entity, persistant, user); // replace or add the modified entity evt.putStore((Entity) dependant); } if (toRemove) { try { dependant.commitRemove(type); } catch (CannotExistWithoutTypeException ex) { // getLogger().warn(ex.getMessage(),ex); } } else { dependant.commitChange(type); } } // add all objects that are dependet on a user and can be safely removed and are added to the remove list private void addRemovedUserDependant(UpdateEvent updateEvt, EntityStore store, User user) { PreferencesImpl preferences = cache.getPreferencesForUserId(user.getId()); // remove preferences of user if (preferences != null) { updateEvt.putRemove(preferences); } List<Entity> referencingEntities = getReferencingEntities(user, store); Iterator<Entity> it = referencingEntities.iterator(); List<Allocatable> templates = new ArrayList<Allocatable>(); while (it.hasNext()) { Entity entity = it.next(); // Remove internal resources automatically if the owner is deleted if (entity instanceof Classifiable && entity instanceof Ownable) { Classification classification = ((Classifiable) entity).getClassification(); DynamicType type = classification.getType(); // remove all internal resources (e.g. templates) that have the user as owner if (((DynamicTypeImpl) type).isInternal()) { User owner = ((Ownable) entity).getOwner(); if (owner != null && owner.equals(user)) { updateEvt.putRemove(entity); if (type.getKey().equals(StorageOperator.RAPLA_TEMPLATE)) { templates.add((Allocatable) entity); } continue; } } } // change the lastchangedby for all objects that are last edited by the user. Change last changed to null if (entity instanceof Timestamp) { Timestamp timestamp = (Timestamp) entity; User lastChangedBy = timestamp.getLastChangedBy(); if (lastChangedBy == null || !lastChangedBy.equals(user)) { continue; } if (entity instanceof Ownable) { User owner = ((Ownable) entity).getOwner(); // we do nothing if the user is also owner, that dependencies need to be resolved manually if (owner != null && owner.equals(user)) { continue; } } // check if entity is already in updateEvent (e.g. is modified) if (updateEvt.getStoreObjects().contains(entity)) { ((SimpleEntity) updateEvt.findEntity(entity)).setLastChangedBy(null); } else { @SuppressWarnings("unchecked") Class<? extends Entity> typeClass = entity.getRaplaType().getTypeClass(); Entity persistant = cache.tryResolve(entity.getId(), typeClass); Entity dependant = editObject(entity, persistant, user); ((SimpleEntity) dependant).setLastChangedBy(null); updateEvt.putStore(entity); } } } // now delete template events for the removed templates for (Allocatable template : templates) { String templateId = template.getId(); Collection<Reservation> reservations = cache.getReservations(); for (Reservation reservation : reservations) { if (reservation.getAnnotation(RaplaObjectAnnotations.KEY_TEMPLATE, "").equals(templateId)) { updateEvt.putRemove(reservation); } } } } /** * returns all entities that depend one the passed entities. In most cases * one object depends on an other object if it has a reference to it. * * @param entity */ final protected Set<Entity> getDependencies(Entity entity, EntityStore store) { RaplaType type = entity.getRaplaType(); final Collection<Entity> referencingEntities; if (Category.TYPE == type || DynamicType.TYPE == type || Allocatable.TYPE == type || User.TYPE == type) { HashSet<Entity> dependencyList = new HashSet<Entity>(); referencingEntities = getReferencingEntities(entity, store); dependencyList.addAll(referencingEntities); return dependencyList; } return Collections.emptySet(); } private List<Entity> getReferencingEntities(Entity entity, EntityStore store) { List<Entity> result = new ArrayList<Entity>(); addReferers(cache.getReservations(), entity, result); addReferers(cache.getAllocatables(), entity, result); Collection<User> users = cache.getUsers(); addReferers(users, entity, result); addReferers(cache.getDynamicTypes(), entity, result); List<Preferences> preferenceList = new ArrayList<Preferences>(); for (User user : users) { PreferencesImpl preferences = cache.getPreferencesForUserId(user.getId()); if (preferences != null) { preferenceList.add(preferences); } } PreferencesImpl systemPreferences = cache.getPreferencesForUserId(null); if (systemPreferences != null) { preferenceList.add(systemPreferences); } addReferers(preferenceList, entity, result); return result; } private void addReferers(Iterable<? extends Entity> refererList, Entity object, List<Entity> result) { for (Entity referer : refererList) { if (referer != null && !referer.isIdentical(object)) { for (ReferenceInfo info : ((EntityReferencer) referer).getReferenceInfo()) { if (info.isReferenceOf(object)) { result.add(referer); } } } } } private int countDynamicTypes(Collection<? extends RaplaObject> entities, Set<String> classificationTypes) throws RaplaException { Iterator<? extends RaplaObject> it = entities.iterator(); int count = 0; while (it.hasNext()) { RaplaObject entity = it.next(); if (DynamicType.TYPE != entity.getRaplaType()) continue; DynamicType type = (DynamicType) entity; String annotation = type.getAnnotation(DynamicTypeAnnotations.KEY_CLASSIFICATION_TYPE); if (annotation == null) { throw new RaplaException(DynamicTypeAnnotations.KEY_CLASSIFICATION_TYPE + " not set for " + type); } if (classificationTypes.contains(annotation)) { count++; } } return count; } // Count dynamic-types to ensure that there is least one dynamic type left private void checkDynamicType(Collection<Entity> entities, Set<String> classificationTypes) throws RaplaException { int count = countDynamicTypes(entities, classificationTypes); Collection<? extends DynamicType> allTypes = cache.getDynamicTypes(); int countAll = countDynamicTypes(allTypes, classificationTypes); if (count >= 0 && count >= countAll) { throw new RaplaException(i18n.getString("error.one_type_requiered")); } } /** * Check if the references of each entity refers to an object in cache or in * the passed collection. */ final protected void setResolverAndCheckReferences(UpdateEvent evt, EntityStore store) throws RaplaException { for (EntityReferencer entity : evt.getEntityReferences()) { entity.setResolver(store); for (ReferenceInfo info : entity.getReferenceInfo()) { String id = info.getId(); // Reference in cache or store? if (store.tryResolve(id, info.getType()) != null) continue; throw new EntityNotFoundException( i18n.format("error.reference_not_stored", info.getType() + ":" + id)); } } } /** * check if we find an object with the same name. If a different object * (different id) with the same unique attributes is found a * UniqueKeyException will be thrown. */ final protected void checkUnique(final UpdateEvent evt, final EntityStore store) throws RaplaException { for (Entity entity : evt.getStoreObjects()) { String name = ""; Entity entity2 = null; if (DynamicType.TYPE == entity.getRaplaType()) { DynamicType type = (DynamicType) entity; name = type.getKey(); entity2 = (Entity) store.getDynamicType(name); if (entity2 != null && !entity2.equals(entity)) throwNotUnique(name); } if (Category.TYPE == entity.getRaplaType()) { Category category = (Category) entity; Category[] categories = category.getCategories(); for (int i = 0; i < categories.length; i++) { String key = categories[i].getKey(); for (int j = i + 1; j < categories.length; j++) { String key2 = categories[j].getKey(); if (key == key2 || (key != null && key.equals(key2))) { throwNotUnique(key); } } } } if (User.TYPE == entity.getRaplaType()) { name = ((User) entity).getUsername(); if (name == null || name.trim().length() == 0) { String message = i18n.format("error.no_entry_for", getString("username")); throw new RaplaException(message); } // FIXME Replace with store.getUserFromRequest for the rare case that two users with the same username are stored in one operation entity2 = cache.getUser(name); if (entity2 != null && !entity2.equals(entity)) throwNotUnique(name); } } } private void throwNotUnique(String name) throws UniqueKeyException { throw new UniqueKeyException(i18n.format("error.not_unique", name)); } /** * compares the version of the cached entities with the versions of the new * entities. Throws an Exception if the newVersion != cachedVersion */ protected void checkVersions(Collection<Entity> entities) throws RaplaException { for (Entity entity : entities) { // Check Versions Entity persistantVersion = findPersistant(entity); // If the entities are newer, everything is o.k. if (persistantVersion != null && persistantVersion != entity) { if ((persistantVersion instanceof Timestamp)) { Date lastChangeTimePersistant = ((LastChangedTimestamp) persistantVersion).getLastChanged(); Date lastChangeTime = ((LastChangedTimestamp) entity).getLastChanged(); if (lastChangeTimePersistant != null && lastChangeTime != null && lastChangeTimePersistant.after(lastChangeTime)) { getLogger() .warn("There is a newer version for: " + entity.getId() + " stored version :" + SerializableDateTimeFormat.INSTANCE .formatTimestamp(lastChangeTimePersistant) + " version to store :" + SerializableDateTimeFormat.INSTANCE.formatTimestamp(lastChangeTime)); throw new RaplaNewVersionException( getI18n().format("error.new_version", entity.toString())); } } } } } protected void removeInconsistentEntities(LocalCache cache, Collection<Entity> list) { for (Iterator iterator = list.iterator(); iterator.hasNext();) { Entity entity = (Entity) iterator.next(); try { checkConsitency(entity, cache.getSuperCategory()); } catch (RaplaException e) { if (entity instanceof Reservation) { getLogger().error("Not loading entity with id: " + entity.getId(), e); cache.remove(entity); iterator.remove(); } } } } /** Check if the objects are consistent, so that they can be safely stored. */ /** * @param evt * @param store * @throws RaplaException */ protected void checkConsistency(UpdateEvent evt, EntityStore store) throws RaplaException { Collection<EntityReferencer> entityReferences = evt.getEntityReferences(); for (EntityReferencer referencer : entityReferences) { for (ReferenceInfo referenceInfo : referencer.getReferenceInfo()) { Entity reference = store.resolve(referenceInfo.getId(), referenceInfo.getType()); if (reference instanceof Preferences || reference instanceof Conflict || reference instanceof Reservation || reference instanceof Appointment) { throw new RaplaException( "The current version of Rapla doesn't allow references to objects of type " + reference.getRaplaType()); } } } for (Entity entity : evt.getStoreObjects()) { checkConsitency(entity, store.getSuperCategory()); } } protected void checkConsitency(Entity entity, Category superCategory) throws RaplaException { RaplaType raplaType = entity.getRaplaType(); if (Category.TYPE == raplaType) { if (entity.equals(superCategory)) { // Check if the user group is missing Category userGroups = ((Category) entity).getCategory(Permission.GROUP_CATEGORY_KEY); if (userGroups == null) { throw new RaplaException( "The category with the key '" + Permission.GROUP_CATEGORY_KEY + "' is missing."); } } else { // check if the category to be stored has a parent Category category = (Category) entity; Category parent = category.getParent(); if (parent == null) { throw new RaplaException("The category " + category + " needs a parent."); } else { int i = 0; while (true) { if (parent == null) { throw new RaplaException("Category needs to be a child of super category."); } else if (parent.equals(superCategory)) { break; } parent = parent.getParent(); i++; if (i > 80) { throw new RaplaException("infinite recursion detection for category " + category); } } } } } else if (Reservation.TYPE == raplaType) { Reservation reservation = (Reservation) entity; checkReservation(reservation); } else if (DynamicType.TYPE == raplaType) { DynamicType type = (DynamicType) entity; DynamicTypeImpl.validate(type, i18n); } else if (Allocatable.TYPE == raplaType) { final Classification classification = ((Allocatable) entity).getClassification(); if (classification.getType().getKey().equals(PERIOD_TYPE)) { String keyName = null; if (classification.getValue("start") == null) { keyName = getString("start_date"); } else if (classification.getValue("end") == null) { keyName = getString("end_date"); } else if (classification.getValue("name") == null || classification.getValue("name").toString().trim().isEmpty()) { keyName = getString("name"); } if (keyName != null) { throw new RaplaException(getI18n().format("error.no_entry_for", keyName)); } } } } protected void checkReservation(Reservation reservation) throws RaplaException { Locale locale = i18n.getLocale(); String name = reservation.getName(locale); if (reservation.getAppointments().length == 0) { throw new RaplaException( i18n.getString("error.no_appointment") + " " + name + " [" + reservation.getId() + "]"); } } protected void checkNoDependencies(final UpdateEvent evt, final EntityStore store) throws RaplaException { Collection<String> removedIds = evt.getRemoveIds(); Collection<Entity> storeObjects = new HashSet<Entity>(evt.getStoreObjects()); HashSet<Entity> dep = new HashSet<Entity>(); Set<Entity> deletedCategories = getDeletedCategories(storeObjects); Collection<Entity> removeEntities = new ArrayList<Entity>(); for (String id : removedIds) { Entity persistant = store.tryResolve(id); if (persistant != null) { removeEntities.add(persistant); } } IterableChain<Entity> iteratorChain = new IterableChain<Entity>(deletedCategories, removeEntities); for (Entity entity : iteratorChain) { // First we add the dependencies from the stored object list for (Entity obj : storeObjects) { if (obj instanceof EntityReferencer) { if (isRefering((EntityReferencer) obj, entity)) { dep.add(obj); } } } // we check if the user deletes himself if (entity instanceof User) { String eventUserId = evt.getUserId(); if (eventUserId != null && eventUserId.equals(entity.getId())) { List<String> emptyList = Collections.emptyList(); throw new DependencyException(i18n.getString("error.deletehimself"), emptyList); } } // Than we add the dependencies from the cache. It is important that // we don't add the dependencies from the stored object list here, // because a dependency could be removed in a stored object Set<Entity> dependencies = getDependencies(entity, store); for (Entity dependency : dependencies) { if (!storeObjects.contains(dependency) && !removeEntities.contains(dependency)) { // only add the first 21 dependencies; if (dep.size() > MAX_DEPENDENCY) { break; } dep.add(dependency); } } } // CKO We skip this check as the admin should have the possibility to deny a user read to allocatables objects even if he has reserved it prior // for (Entity entity : storeObjects) { // if ( entity.getRaplaType() == Allocatable.TYPE) // { // Allocatable alloc = (Allocatable) entity; // for (Entity reference:getDependencies(entity)) // { // if ( reference instanceof Ownable) // { // User user = ((Ownable) reference).getOwner(); // if (user != null && !alloc.canReadOnlyInformation(user)) // { // throw new DependencyException( "User " + user.getUsername() + " refers to " + getName(alloc) + ". Read permission is required.", Collections.singleton( getDependentName(reference))); // } // } // } // } // } if (dep.size() > 0) { Collection<String> names = new ArrayList<String>(); for (Entity obj : dep) { String string = getDependentName(obj); names.add(string); } throw new DependencyException(getString("error.dependencies"), names.toArray(new String[] {})); } // Count dynamic-types to ensure that there is least one dynamic type // for reservations and one for resources or persons checkDynamicType(removeEntities, Collections.singleton(DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RESERVATION)); checkDynamicType(removeEntities, new HashSet<String>( Arrays.asList(new String[] { DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RESOURCE, DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_PERSON }))); } private boolean isRefering(EntityReferencer referencer, Entity entity) { for (ReferenceInfo info : referencer.getReferenceInfo()) { if (info.isReferenceOf(entity)) { return true; } } return false; } private Set<Entity> getDeletedCategories(Iterable<Entity> storeObjects) { Set<Entity> deletedCategories = new HashSet<Entity>(); for (Entity entity : storeObjects) { if (entity.getRaplaType() == Category.TYPE) { Category newCat = (Category) entity; Category old = tryResolve(entity.getId(), Category.class); if (old != null) { Set<Category> oldSet = getAllCategories(old); Set<Category> newSet = getAllCategories(newCat); oldSet.removeAll(newSet); deletedCategories.addAll(oldSet); } } } return deletedCategories; } private Set<Category> getAllCategories(Category old) { HashSet<Category> result = new HashSet<Category>(); result.add(old); for (Category child : old.getCategories()) { result.addAll(getAllCategories(child)); } return result; } protected String getDependentName(Entity obj) { Locale locale = raplaLocale.getLocale(); StringBuffer buf = new StringBuffer(); if (obj instanceof Reservation) { buf.append(getString("reservation")); } else if (obj instanceof Preferences) { buf.append(getString("preferences")); } else if (obj instanceof Category) { buf.append(getString("categorie")); } else if (obj instanceof Allocatable) { buf.append(getString("resources_persons")); } else if (obj instanceof User) { buf.append(getString("user")); } else if (obj instanceof DynamicType) { buf.append(getString("dynamictype")); } if (obj instanceof Named) { final String string = ((Named) obj).getName(locale); buf.append(": " + string); } else { buf.append(obj.toString()); } if (obj instanceof Reservation) { Reservation reservation = (Reservation) obj; Appointment[] appointments = reservation.getAppointments(); if (appointments.length > 0) { buf.append(" "); Date start = appointments[0].getStart(); buf.append(raplaLocale.formatDate(start)); } String template = reservation.getAnnotation(RaplaObjectAnnotations.KEY_TEMPLATE); if (template != null) { Allocatable templateObj = tryResolve(template, Allocatable.class); if (templateObj != null) { String name = templateObj.getName(locale); buf.append(" in template " + name); User owner = templateObj.getOwner(); if (owner != null) { buf.append(" of user " + owner.getUsername()); } } else { buf.append(" in template " + template); } } } final Object idFull = obj.getId(); if (idFull != null) { String idShort = idFull.toString(); int dot = idShort.lastIndexOf('.'); buf.append(" (" + idShort.substring(dot + 1) + ")"); } String string = buf.toString(); return string; } private void storeUser(User refUser) throws RaplaException { ArrayList<Entity> arrayList = new ArrayList<Entity>(); arrayList.add(refUser); Collection<Entity> storeObjects = arrayList; Collection<Entity> removeObjects = Collections.emptySet(); storeAndRemove(storeObjects, removeObjects, null); } public static String encrypt(String encryption, String password) throws RaplaException { MessageDigest md; try { md = MessageDigest.getInstance(encryption); } catch (NoSuchAlgorithmException ex) { throw new RaplaException(ex); } synchronized (md) { md.reset(); md.update(password.getBytes()); return encryption + ":" + Tools.convert(md.digest()); } } private boolean checkPassword(String userId, String password) throws RaplaException { if (userId == null) return false; String correct_pw = cache.getPassword(userId); if (correct_pw == null) { return false; } if (correct_pw.equals(password)) { return true; } int columIndex = correct_pw.indexOf(":"); if (columIndex > 0 && correct_pw.length() > 20) { String encryptionGuess = correct_pw.substring(0, columIndex); if (encryptionGuess.contains("sha") || encryptionGuess.contains("md5")) { password = encrypt(encryptionGuess, password); if (correct_pw.equals(password)) { return true; } } } return false; } @Override public FutureResult<Map<Allocatable, Collection<Appointment>>> getFirstAllocatableBindings( Collection<Allocatable> allocatables, Collection<Appointment> appointments, Collection<Reservation> ignoreList) { Lock readLock; try { readLock = readLock(); } catch (RaplaException e) { return new ResultImpl<Map<Allocatable, Collection<Appointment>>>(e); } Map<Allocatable, Map<Appointment, Collection<Appointment>>> allocatableBindings; try { allocatableBindings = getAllocatableBindings(allocatables, appointments, ignoreList, true); } finally { unlock(readLock); } Map<Allocatable, Collection<Appointment>> map = new HashMap<Allocatable, Collection<Appointment>>(); for (Map.Entry<Allocatable, Map<Appointment, Collection<Appointment>>> entry : allocatableBindings .entrySet()) { Allocatable alloc = entry.getKey(); Collection<Appointment> list = entry.getValue().keySet(); map.put(alloc, list); } return new ResultImpl<Map<Allocatable, Collection<Appointment>>>(map); } @Override public FutureResult<Map<Allocatable, Map<Appointment, Collection<Appointment>>>> getAllAllocatableBindings( Collection<Allocatable> allocatables, Collection<Appointment> appointments, Collection<Reservation> ignoreList) throws RaplaException { try { Lock readLock = readLock(); try { Map<Allocatable, Map<Appointment, Collection<Appointment>>> allocatableBindings = getAllocatableBindings( allocatables, appointments, ignoreList, false); return new ResultImpl<Map<Allocatable, Map<Appointment, Collection<Appointment>>>>( allocatableBindings); } finally { unlock(readLock); } } catch (Exception e) { return new ResultImpl<Map<Allocatable, Map<Appointment, Collection<Appointment>>>>(e); } } public Map<Allocatable, Map<Appointment, Collection<Appointment>>> getAllocatableBindings( Collection<Allocatable> allocatables, Collection<Appointment> appointments, Collection<Reservation> ignoreList, boolean onlyFirstConflictingAppointment) { Map<Allocatable, Map<Appointment, Collection<Appointment>>> map = new HashMap<Allocatable, Map<Appointment, Collection<Appointment>>>(); for (Allocatable allocatable : allocatables) { String annotation = allocatable.getAnnotation(ResourceAnnotations.KEY_CONFLICT_CREATION); boolean holdBackConflicts = annotation != null && annotation.equals(ResourceAnnotations.VALUE_CONFLICT_CREATION_IGNORE); if (holdBackConflicts) { continue; } SortedSet<Appointment> appointmentSet = getAppointments(allocatable); if (appointmentSet == null) { continue; } map.put(allocatable, new HashMap<Appointment, Collection<Appointment>>()); for (Appointment appointment : appointments) { Set<Appointment> conflictingAppointments = AppointmentImpl.getConflictingAppointments( appointmentSet, appointment, ignoreList, onlyFirstConflictingAppointment); if (conflictingAppointments.size() > 0) { Map<Appointment, Collection<Appointment>> appMap = map.get(allocatable); if (appMap == null) { appMap = new HashMap<Appointment, Collection<Appointment>>(); map.put(allocatable, appMap); } appMap.put(appointment, conflictingAppointments); } } } return map; } @Override public FutureResult<Date> getNextAllocatableDate(Collection<Allocatable> allocatables, Appointment appointment, Collection<Reservation> ignoreList, Integer worktimeStartMinutes, Integer worktimeEndMinutes, Integer[] excludedDays, Integer rowsPerHour) { try { Lock readLock = readLock(); try { Appointment newState = appointment; Date firstStart = appointment.getStart(); boolean startDateExcluded = isExcluded(excludedDays, firstStart); boolean wholeDay = appointment.isWholeDaysSet(); boolean inWorktime = inWorktime(appointment, worktimeStartMinutes, worktimeEndMinutes); if (rowsPerHour == null || rowsPerHour <= 1) { rowsPerHour = 1; } for (int i = 0; i < 366 * 24 * rowsPerHour; i++) { newState = ((AppointmentImpl) newState).clone(); Date start = newState.getStart(); long millisToAdd = wholeDay ? DateTools.MILLISECONDS_PER_DAY : (DateTools.MILLISECONDS_PER_HOUR / rowsPerHour); Date newStart = new Date(start.getTime() + millisToAdd); if (!startDateExcluded && isExcluded(excludedDays, newStart)) { continue; } newState.move(newStart); if (!wholeDay && inWorktime && !inWorktime(newState, worktimeStartMinutes, worktimeEndMinutes)) { continue; } if (!isAllocated(allocatables, newState, ignoreList)) { return new ResultImpl<Date>(newStart); } } return new ResultImpl<Date>((Date) null); } finally { unlock(readLock); } } catch (Exception ex) { return new ResultImpl<Date>(ex); } } private boolean inWorktime(Appointment appointment, Integer worktimeStartMinutes, Integer worktimeEndMinutes) { long start = appointment.getStart().getTime(); int minuteOfDayStart = DateTools.getMinuteOfDay(start); long end = appointment.getEnd().getTime(); int minuteOfDayEnd = DateTools.getMinuteOfDay(end) + (int) DateTools.countDays(start, end) * 24 * 60; boolean inWorktime = (worktimeStartMinutes == null || worktimeStartMinutes <= minuteOfDayStart) && (worktimeEndMinutes == null || worktimeEndMinutes >= minuteOfDayEnd); return inWorktime; } private boolean isExcluded(Integer[] excludedDays, Date date) { Integer weekday = DateTools.getWeekday(date); if (excludedDays != null) { for (Integer day : excludedDays) { if (day.equals(weekday)) { return true; } } } return false; } private boolean isAllocated(Collection<Allocatable> allocatables, Appointment appointment, Collection<Reservation> ignoreList) throws Exception { FutureResult<Map<Allocatable, Collection<Appointment>>> firstAllocatableBindingsResult = getFirstAllocatableBindings( allocatables, Collections.singleton(appointment), ignoreList); Map<Allocatable, Collection<Appointment>> firstAllocatableBindings = firstAllocatableBindingsResult.get(); for (Map.Entry<Allocatable, Collection<Appointment>> entry : firstAllocatableBindings.entrySet()) { if (entry.getValue().size() > 0) { return true; } } return false; } public Collection<Entity> getVisibleEntities(final User user) throws RaplaException { Lock readLock = readLock(); try { return cache.getVisibleEntities(user); } finally { unlock(readLock); } } // this check is only there to detect rapla bugs in the conflict api and can be removed if it causes performance issues private void checkAbandonedAppointments() { Collection<? extends Allocatable> allocatables = cache.getAllocatables(); Logger logger = getLogger().getChildLogger("appointmentcheck"); try { for (Allocatable allocatable : allocatables) { SortedSet<Appointment> appointmentSet = this.appointmentMap.get(allocatable.getId()); if (appointmentSet == null) { continue; } for (Appointment app : appointmentSet) { { SimpleEntity original = (SimpleEntity) app; String id = original.getId(); if (id == null) { logger.error("Empty id for " + original); continue; } Appointment persistant = cache.tryResolve(id, Appointment.class); if (persistant == null) { logger.error("appointment not stored in cache " + original); continue; } } Reservation reservation = app.getReservation(); if (reservation == null) { logger.error("Appointment without a reservation stored in cache " + app); appointmentSet.remove(app); continue; } else if (!reservation.hasAllocated(allocatable, app)) { logger.error("Allocation is not stored correctly for " + reservation + " " + app + " " + allocatable + " removing binding for " + app); appointmentSet.remove(app); continue; } else { { Reservation original = reservation; String id = original.getId(); if (id == null) { logger.error("Empty id for " + original); continue; } Reservation persistant = cache.tryResolve(id, Reservation.class); if (persistant != null) { Date lastChanged = original.getLastChanged(); Date persistantLastChanged = persistant.getLastChanged(); if (persistantLastChanged != null && !persistantLastChanged.equals(lastChanged)) { logger.error( "Reservation stored in cache is not the same as in allocation store " + original); continue; } } else { logger.error("Reservation not stored in cache " + original + " removing binding for " + app); appointmentSet.remove(app); continue; } } } } } } catch (Exception ex) { logger.error(ex.getMessage(), ex); } } @SuppressWarnings("deprecation") private void addDefaultEventPermissions(DynamicTypeImpl dynamicType, Category userGroups) { { Permission permission = dynamicType.newPermission(); permission.setAccessLevel(Permission.READ_TYPE); dynamicType.addPermission(permission); } Category canReadEventsFromOthers = userGroups.getCategory(Permission.GROUP_CAN_READ_EVENTS_FROM_OTHERS); if (canReadEventsFromOthers != null) { Permission permission = dynamicType.newPermission(); permission.setAccessLevel(Permission.READ); permission.setGroup(canReadEventsFromOthers); dynamicType.addPermission(permission); } Category canCreate = userGroups.getCategory(Permission.GROUP_CAN_CREATE_EVENTS); if (canCreate != null) { Permission permission = dynamicType.newPermission(); permission.setAccessLevel(Permission.CREATE); permission.setGroup(canCreate); dynamicType.addPermission(permission); } } protected void createDefaultSystem(LocalCache cache) throws RaplaException { EntityStore store = new EntityStore(null, cache.getSuperCategory()); Date now = getCurrentTimestamp(); PreferencesImpl newPref = new PreferencesImpl(now, now); newPref.setId(PreferencesImpl.getPreferenceIdFromUser(null)); newPref.setResolver(this); newPref.putEntry(CalendarModel.ONLY_MY_EVENTS_DEFAULT, false); newPref.setReadOnly(); store.put(newPref); @SuppressWarnings("deprecation") String[] userGroups = new String[] { Permission.GROUP_CAN_READ_EVENTS_FROM_OTHERS, Permission.GROUP_CAN_CREATE_EVENTS }; CategoryImpl groupsCategory = new CategoryImpl(now, now); groupsCategory.setKey("user-groups"); setName(groupsCategory.getName(), groupsCategory.getKey()); setNew(groupsCategory); store.put(groupsCategory); for (String catName : userGroups) { CategoryImpl group = new CategoryImpl(now, now); group.setKey(catName); setNew(group); setName(group.getName(), group.getKey()); groupsCategory.addCategory(group); store.put(group); } cache.getSuperCategory().addCategory(groupsCategory); DynamicTypeImpl resourceType = newDynamicType(DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RESOURCE, "resource", groupsCategory); setName(resourceType.getName(), "resource"); add(store, resourceType); DynamicTypeImpl personType = newDynamicType(DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_PERSON, "person", groupsCategory); setName(personType.getName(), "person"); add(store, personType); DynamicTypeImpl eventType = newDynamicType(DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RESERVATION, "event", groupsCategory); setName(eventType.getName(), "event"); add(store, eventType); UserImpl admin = new UserImpl(now, now); admin.setUsername("admin"); admin.setAdmin(true); setNew(admin); store.put(admin); Collection<Entity> list = store.getList(); cache.putAll(list); testResolve(list); setResolver(list); UserImpl user = cache.getUser("admin"); String password = ""; cache.putPassword(user.getId(), password); cache.getSuperCategory().setReadOnly(); AllocatableImpl allocatable = new AllocatableImpl(now, now); allocatable.setResolver(this); DynamicType dynamicType = cache.getDynamicType("resource"); Classification classification = dynamicType.newClassification(); allocatable.setClassification(classification); PermissionContainer.Util.copyPermissions(dynamicType, allocatable); setNew(allocatable); classification.setValue("name", getString("test_resource")); allocatable.setOwner(user); cache.put(allocatable); } private void add(EntityStore list, DynamicTypeImpl type) { list.put(type); for (Attribute att : type.getAttributes()) { list.put((Entity) att); } } private Attribute createStringAttribute(String key, String name) throws RaplaException { Attribute attribute = newAttribute(AttributeType.STRING, null); attribute.setKey(key); setName(attribute.getName(), name); return attribute; } private Attribute addAttributeWithInternalId(DynamicType dynamicType, String key, AttributeType type) throws RaplaException { String id = "rapla_" + dynamicType.getKey() + "_" + key; Attribute attribute = newAttribute(type, id); attribute.setKey(key); setName(attribute.getName(), key); dynamicType.addAttribute(attribute); return attribute; } private DynamicTypeImpl newDynamicType(String classificationType, String key, Category userGroups) throws RaplaException { DynamicTypeImpl dynamicType = new DynamicTypeImpl(); dynamicType.setAnnotation("classification-type", classificationType); dynamicType.setKey(key); setNew(dynamicType); if (classificationType.equals(DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RESOURCE)) { dynamicType.addAttribute(createStringAttribute("name", "name")); dynamicType.setAnnotation(DynamicTypeAnnotations.KEY_NAME_FORMAT, "{name}"); dynamicType.setAnnotation(DynamicTypeAnnotations.KEY_COLORS, "automatic"); addDefaultResourcePermissions(dynamicType); } else if (classificationType.equals(DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_RESERVATION)) { dynamicType.addAttribute(createStringAttribute("name", "eventname")); dynamicType.setAnnotation(DynamicTypeAnnotations.KEY_NAME_FORMAT, "{name}"); dynamicType.setAnnotation(DynamicTypeAnnotations.KEY_COLORS, null); addDefaultEventPermissions(dynamicType, userGroups); } else if (classificationType.equals(DynamicTypeAnnotations.VALUE_CLASSIFICATION_TYPE_PERSON)) { dynamicType.addAttribute(createStringAttribute("surname", "surname")); dynamicType.addAttribute(createStringAttribute("firstname", "firstname")); dynamicType.addAttribute(createStringAttribute("email", "email")); dynamicType.setAnnotation(DynamicTypeAnnotations.KEY_NAME_FORMAT, "{surname} {firstname}"); dynamicType.setAnnotation(DynamicTypeAnnotations.KEY_COLORS, null); addDefaultResourcePermissions(dynamicType); } dynamicType.setResolver(this); return dynamicType; } private void addDefaultResourcePermissions(DynamicTypeImpl dynamicType) { { Permission permission = dynamicType.newPermission(); permission.setAccessLevel(Permission.READ_TYPE); dynamicType.addPermission(permission); } { Permission permission = dynamicType.newPermission(); permission.setAccessLevel(Permission.ALLOCATE_CONFLICTS); dynamicType.addPermission(permission); } } private Attribute newAttribute(AttributeType attributeType, String id) throws RaplaException { AttributeImpl attribute = new AttributeImpl(attributeType); if (id == null) { setNew(attribute); } else { ((RefEntity) attribute).setId(id); } attribute.setResolver(this); return attribute; } private <T extends Entity> void setNew(T entity) throws RaplaException { RaplaType raplaType = entity.getRaplaType(); String id = createIdentifier(raplaType, 1)[0]; ((RefEntity) entity).setId(id); } private void setName(MultiLanguageName name, String to) { String currentLang = i18n.getLang(); name.setName("en", to); try { String translation = i18n.getString(to); name.setName(currentLang, translation); } catch (Exception ex) { } } protected void convertTemplates() { for (Reservation reservations : cache.getReservations()) ; } public void fillConflictDisableInformation(User user, Conflict conflict) { cache.fillConflictDisableInformation(user, conflict); } }