Министерство образования и науки РФ
Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования
«Рыбинский государственный авиационный технический университет
имени П.А. Соловьева»
Факультет радиоэлектроники и информатики
Кафедра Математического и программного обеспечения электронных вычислительных средств
Отчет по учебной практике
Студент группы ИПБ-13
(Код) (Подпись, дата) (Фамилия И.О.)
Руководитель ст. преп.
(Уч. степень, звание) (Подпись, дата) (Фамилия И. О.)
(Подпись, дата) (Фамилия И. О.)
Оглавление
1. Описание работы программиста на предприятии.. 4
2. Используемые технологии и инструментарии.. 5
2.1 Грид-платформа Infinispan. 5
4 Функциональные требования. 9
4.1 Требования к местоположению.. 9
5. Описание методики тестирования и всех тестов. 11
6. Результаты тестирования. 12
Введение
Многие Web-приложения создаются путем переписывания приложений для настольных ПК. В идеальном случае Web-приложения должны быть столь же быстрыми и масштабируемыми, как и их «десктопные» версии. Скорость работы почти каждого Web-приложения может быть существенно повышена. Кэширование часто просматриваемых данных, которые изменяются достаточно редко, является мощным способом для уменьшения времени ожидания пользователей.
В отличие от кэша первого уровня, который по умолчанию используется в Hibernate, кэш второго уровня нужно включать непосредственно в настройках Hibernate, и он реализуется провайдером, сторонней библиотекой кэшем. В данном случае - это Infinispan.
Когда сессии Hibernate нужно загрузить сущность, она всегда ищет кэшированную копию сущности в кэше первого уровня. Если копия сущности существует в кэше первого уровня, то возвращается эта копия, если нет, то поиск продолжается в кэше второго уровня. Если нашли в кэше второго уровня, то возвращается из кэша второго уровня, но сначала перед этим записывается, копия сущности в кэш первого уровня. Соответственно, если нет копий сущности ни в кэше первого уровня, не второго уровня, выполняется Sql запрос и возвращается сущность из таблицы баз данных, но перед этим, копия сущности сохраняется в кэше первого и второго уровня. В кэш второго уровня заносятся все изменения, которые были сделаны в сессии. Кеш второго уровня может стать не актуальным, если таблицу базы данных редактируют напрямую Sql запросами, в обход hibernate сессии, в таком случаи нужно обнулять/затирать кэш. Кэш второго уровня можно сконфигурировать двумя способами. Первый способ, как кластерный кэш или распределенный кэш который используется двумя или более программами, т.е. программы работают на разных виртуальных машинах (JVM), используя разные “области памяти“, но при этом работают с общим кэшем. Второй способ проще, используем одну область памяти на одной виртуальной машине (JVM), так сказать sessionFactory кэш. Кэшировать можно как сущности, так и коллекции сущностей (many-to-one, one-to-one и т.д.).
1. Описание работы программиста на предприятии
На предприятии ООО «НПО «Криста» используется «Итерационная модель процесса разработки». Эта модель представляет работу на проекте в циклическом характере. Каждый цикл состоит из 4 фаз и завершается выпуском очередной версии продукта. Фазы представляют совокупность связанных действий, которые завершаются вехой. Вехи являются контрольными точками процесса. Это время проведения инспекций (фазовых обзоров), на которых обсужда-ются достигнутые результаты и принимаются решения. Достижение каждой вехи сопровожда-ется созданием определенных материалов, которые обязательно согласовываются всеми лидерами групп и заказчиком.
Данная модель в делится на следующие вехи:
1) Общее описание проекта. Эта веха позволяет заказчику и исполнителям прийти к общему пониманию.
2) Функциональные спецификации. Целью этой стадии проекта является определе¬ние с максимально возможной точностью того, что строится, как это должно быть построено и сколько времени на это потребуется.
3) Завершение кодирования. Целью является построение системы, описанную в Функ-циональных спецификациях. Цель считается достигнутой, когда все функ¬циональные возможности системы реализованы и оптимизированы.
4) Выпуск продукта. Эта фаза проекта является фазой стабилизации: Никакие новые возможности в проект не добавляются. Цель этой фазы – передача созданной сис¬темы службам технической поддержки.
2. Используемые технологии и инструментарии
2.1 Грид-платформа Infinispan
В качестве инструмента кэширования использовалась middleware библиотека Infinispan. Infinispan - это масштабируемое, высокодоступное хранилище данных типа ключ/значение с открытым исходным кодом написанное на Java. Разработчики программного обеспечения обычно используют Infinispan в качестве повышающей производительность системы распределенного кэширования данных с использованием оперативной памяти, находящейся над более требовательным к ресурсам и медленным хранилищем, таким, как реляционная база данных. Главной причиной рассмотрения возможности использования Infinispan вместе с программным обеспечением является необходимость повышения производительности, для быстрого доступа к данным с малыми задержками.
2.2 Класс LinksEJB
В моем распоряжении есть готовый класс реализующий кэширование связей на уровне hibernate - LinksEJB.
3. Спецификация
3.1 UML диаграммы
(Рис. 1) Диаграмма класса кэширования LinksEJB до решения задачи
На диаграмме виден класс LinksEJB, который реализует кэширование связей документов на уровне Hibernate. Для организации непосредственно кэширования класс LinksEJB реализует интерфейс LinksRemote, который реализует «удаленный интерфейс» EJB-компоненты на сервере. Располагается класс в пакете retools-ejb и находится в серверной части приложения для удобного хранения кэша и взаимодействия с Hibernate. те ить.с
(Рис. 2) Диаграмма класса кэширования LinksManager после решения задачи
Класс LinksManager реализует кэширование второго уровня с использованием стороннего провайдера, библиотеки кэша. Дополнительным атрибутом здесь являются cacheManager и cacheName, сам контейнер кэша и его имя соответственно. Так как мы отказались от Hibernate, нам больше не нужен интерфейс LinksRemote и мы можем его спокойно удалить. Поскольку Infinispan - это middleware библиотека, она должна располагаться между серверной и веб частями приложения, соответственно необходимо перенести класс в соответствующий пакет – retools-common.
Связь - это запись в отдельной специализированной таблице связей, в которой находится:
o идентификатор родительского документа (или детализации документа);
o идентификатор дочернего документа (или детализации документа);
o тип связи (задает имя для пары документов).
(Рис. 3) Схематичное представление связи документов
В случае наличия связи у документа (строки детализации) она визуализируется в виде иконки ("цепь"). Пользователь имеет возможность перейти на документы, связанные с текущим документом. Если документ (строка детализации) связана только с одним классом документов, то при нажатии на "цепь" осуществляется переход на соответствующий интерфейс ввода (если связь только с одним документом, то он открывается в режиме карточки, если связь с двумя и более документами, то они отрываются в режиме списка с фильтрацией).
По сути, каждый раз, когда требуется проверить наличие связи между документами, нужно отправлять запрос к базе. Документов может быть больше тысячи, и, соответственно, нужно будет отправить более двух тысяч запросов (запросов в два раза больше, так как один документ дочерний, а второй родительский) типа:
select
count(*)
where
uuid1=...
uuid1=...
Сами связи хранятся в базе в отдельной таблице, в общем виде таблица имеет вид:
parentuuid |
childuuid |
linktype |
... |
... |
... |
Было бы удобнее хранить данные о количестве связей с документом в кэше:
Документ 1 : count
(List<uuid>)
3.3 Постановка задачи
Стоит задача реализовать кэширование связей документов с помощью Infinispan.
4 Функциональные требования
4.1 Требования к местоположению
Первым шагом является доработка уже существующего кэширования связей документов, перенос кэширования на Infinispan. Есть готовая реализация кэширования на уровне серверной части приложения. Недостаток такой реализации - это медленное обращение к кэшу в серверной части приложения из веб части приложения, то есть для обращения к кэшу на уровне серверной части приложения нужно сделать запрос к нему с веб, а веб, в свою очередь, отправит результат запроса обратно на серверную часть. Было бы легче, если бы существовал посредник между сервер и веб частью приложения, роль которого и сыграет Infinispan.
(Рис.4) Изображение работы кэширования изначально и в итоге
Для начала пересоздадим класс кэширования. Теперь роль LinksEJB будет играть LinksManager. Необходимо поместить этот класс на промежуточном уровне, для этого перемещаем его из пакета retools-ejb в retools-common.
4.2 Реализация
Добавим Infinispan в приложение и инициализируем CacheContainer. Добавление Infinispan заключается в объявлении зависимости в файле pom.xml:
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-embedded</artifactId>
<version>7.1.0.Final</version>
</dependency>
Теперь нужно поправить реализацию LinksManager для Infinispan. Добавим CacheContainer. CacheContainer действует в качестве контейнера для кэшей и управляет их жизненным циклом.
Инициализировать CacheContainer достаточнопросто:
@Resource(lookup = "java:jboss/infinispan/container/retools")
Аннотации Resource передаем место расположения Конфигураций Infinispan, в которых создаем контейнер для кэша и настраиваем его. Все конфигурации для кэша прописываются в Конфигурациях администраторской консоли в разделе Infinispan —>CacheContainers. Добавляем контейнер с именем links и дефолтным кэшем retools (так как располагается сущность LinksData в retools). Задаем Size (размер контейнера) равный 1 000 000, ExpirationInterval (время существования) 400 (в минутах) и EvictionStrategy (стратегия очистки контейнеров кэша) будет LRU (вытеснение давно неиспользуемых).
LRU (Least Recently Used) - это алгоритм, при котором вытесняются значения, которые дольше всего не запрашивались. Соответственно, необходимо хранить время последнего запроса к значению или хранить элементы в списке. При попадании в кэш элемент перемещается в голову списка. И как только число закэшированных значений превосходит Sizeнеобходимо вытеснить из кеша значение, которое дольше всего не запрашивалось, вытесняется элемент из хвоста списка.
Далее создаем переменную для хранения нашего контейнера:
privateCacheContainercontainer;
С работающим CacheContainer мы можем начать создавать кэш и хранить в нем данные. Кэш в Infinispan с "именем" означает, что он идентифицируется по уникальному имени. Преждевсегонеобходимополучитькэш:
private final Cache<String, LinksData> links = container.getCache(cacheName*);
* ранее прописываем стороковую константу с именем для кэша связи:
private final String cacheName = "links";
Если запрашиваемого кэша не существует в CacheContainer, он будет создан:
private LinksData getLinksData(final String uuid) {
LinksData linksData = links.get(uuid);
if (linksData != null) {
actualizeLinkTypes(linksData);
} else {
linksData = links.getAdvancedCache().computeIfAbsent(uuid, s -> new LinksData());
selectLinks(uuid, linksData);
}
return linksData;
}
Метод очистки кэша clear просто выталкивает кэш из контейнера:
public void clear(String uuid, String ownerUuid) {
links.evict(uuid);
if (ownerUuid != null)
links.evict(ownerUuid);
}
5. Описание методики тестирования и всех тестов
Тестирование будет заключатся в проверке доступности кэша созданного в серверной части приложения из веб части и наоборот. Для этого нужно связать пару документов связью определенного типа. Документы связываются в оба конца, то есть из одного документа может осуществляться переход в другой документ. Далее проверяем наличие связи, то есть наличие иконки "цепь" перед документами. Создаем кэш этой связи, проверим ее доступность из серверной части приложения и из веб части приложения.
№ |
Проверяемая функциональность |
Входные данные |
Ожидаемый результат |
1 |
Происходит ли создание кэша связей документов |
1)Создание документов и процедура создания связи |
Ожидается отображение иконки «цепь» в соответствующей колонке документа |
2 |
Проверка обращения к кэшу |
1)Переход от документа родителя к дочернему документу 2)Переход от дочернего документа к родительскому |
Корректный переход между документами. |
№ |
Полученный результат |
1 |
Иконка «цепь» появилась, значит кэш создался. |
2 |
По иконке «цепь» осуществляется переход между связанными документами. |
Описание результатов тестирования:
Была создана связь между документом "ГБОУ СОШ №4 г. Сызрани" в Учреждениях (ОВ):
и соответствующей ему Группой пользователей для этого учреждения.
Иконки имеются в обоих случаях, соответственно связь создана. Переход выполняется в обе стороны, кэш есть в обоих частях приложения, как в серверной, так и в веб части.
7. Приложения
Листинг класса LinksManager:
package ru.krista.retools.link;
//import com.google.common.cache.Cache;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.eviction.EvictionStrategy;
import ru.krista.budget.model.docstate.DocumentOperation;
import ru.krista.budget.model.docstate.DocumentOperationEnum;
import ru.krista.common.caches.CachesManager;
import ru.krista.core.dataaccess.DataSession;
import ru.krista.core.dataaccess.GroupDataParam;
import ru.krista.core.dataaccess.OrderDataParam;
import ru.krista.core.dataaccess.QueryParams;
import ru.krista.core.dataaccess.beans.anno.DataSess;
import ru.krista.core.model.info.ClassInfo;
import ru.krista.core.shared.filter.EntityAttribute;
import ru.krista.core.shared.filter.Filter;
import ru.krista.core.sql.JtaUtils;
import ru.krista.retools.Domains;
import ru.krista.retools.models.link.Link;
import ru.krista.retools.models.link.LinkType;
import ru.krista.retools.models.link.LinkTypeField;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.transaction.*;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static ru.krista.core.shared.filter.BoolOperand.TRUE;
import static ru.krista.core.shared.filter.ComplexCondition.and;
import static ru.krista.core.shared.filter.ComplexCondition.or;
import static ru.krista.core.shared.filter.EntityAttribute.getSequenceInstance;
import static ru.krista.core.shared.filter.Filter.byId;
import static ru.krista.core.shared.filter.Filter.byUuid;
import static ru.krista.core.shared.filter.Filter.in;
import static ru.krista.core.shared.filter.Relation.eq;
@ApplicationScoped
public class LinksManager implements Links {
private final Domains domains;
private final DataSession session;
private final static ConcurrentHashMap<Long, LinkTypeData> linkTypes = new ConcurrentHashMap<>();
private final Map<Transaction, ChangeStore> registeredStores = new WeakHashMap<>();
private final String cacheName = "links";
private final long typeUpdateInterval = Long.getLong("ru.krista.retools.link.type.updateInterval", 5 * 60 * 1000);
private final int linksCacheSize = Integer.getInteger("ru.krista.retools.link.cacheSize", 1_000_000);
private final long linksCacheExpireTime = Long.getLong("ru.krista.retools.link.expireAfter", 8 * 60);
private static final String NO_OWNER_UUID = "";
private final Cache<String, LinksData> links;
public LinksManager() {
domains = null;
session = null;
links = null;
}
@Inject
public LinksManager(Domains domains, @DataSess(scope = DataSess.REQUEST_SCOPE) DataSession session, CachesManager cacheManager) {
this.domains = domains;
this.session = session;
this.links = cacheManager.getCache(cacheName);
}
private boolean needRefreshLinkType(LinkTypeData mld) {
return mld == null || System.currentTimeMillis() - mld.lastUpdate > typeUpdateInterval;
}
private HashSet<DocumentOperationEnum> getOperations(List<DocumentOperation> operations) {
HashSet<DocumentOperationEnum> parentOperations = new HashSet<>();
for (DocumentOperation operation : operations) {
parentOperations.add(operation.getValue());
}
return parentOperations;
}
private Map<String, List<String>> getWhiteList(List<? extends LinkTypeField> fieldList) {
Map<String, List<String>> result = new HashMap<>();
for (LinkTypeField field : fieldList) {
List<String> list = result.get(field.getPath());
if (list == null) {
result.put(field.getPath(), list = new ArrayList<>());
}
list.add(field.getProperty().getName());
}
return result;
}
private void refreshLinkTypes(List<Long> ids) {
if (ids.isEmpty()) return;
Filter filter;
if (ids.size() == 1) {
filter = byId(ids.get(0));
} else {
filter = new Filter(in(new EntityAttribute("id"), ids));
}
for (LinkType type : session.getObjects(LinkType.class, filter, null)) {
LinkTypeData metaLink;
metaLink = linkTypes.get(type.getId());
if (metaLink == null) {
linkTypes.putIfAbsent(type.getId(), new LinkTypeData());
metaLink = linkTypes.get(type.getId());
}
synchronized (metaLink) {
if (needRefreshLinkType(metaLink)) {
metaLink.lastUpdate = System.currentTimeMillis();
metaLink.code = type.getClsId();
metaLink.name = type.getName();
metaLink.parentDomain = type.getParentDomain().getClassName();
metaLink.parentProperty = type.getParentProperty();
metaLink.childDomain = type.getChildDomain().getClassName();
metaLink.childProperty = type.getChildProperty();
metaLink.parentJumpMessage = type.getParentJumpMessage();
metaLink.childJumpMessage = type.getChildJumpMessage();
metaLink.aggregate = type.getDontShowInCaption() == null || !type.getDontShowInCaption();
metaLink.parentOperations = getOperations(type.getParentOperations());
metaLink.childOperations = getOperations(type.getChildOperations());
metaLink.parentWhiteList = getWhiteList(type.getParentWhiteList());
metaLink.childWhiteList = getWhiteList(type.getChildWhiteList());
}
}
}
}
private void actualizeLinkTypes(LinksData linksData) {
List<Long> ids = new ArrayList<>();
for (Long typeId : linksData.linkData.keySet()) {
if (needRefreshLinkType(linkTypes.get(typeId))) {
ids.add(typeId);
}
}
refreshLinkTypes(ids);
}
private long selectLinkTypeCount(Filter filter) {
List<Object> data = session.groupData(LinkType.class, filter, null, new GroupDataParam().setCounting(true));
return (long) data.get(0);
}
private void refreshLink(LinksData linksData, Long typeId, Ownership ownership, boolean aggregate, String linkUuid,
String linkOwnerUuid, boolean inactive) {
LinkData linkData = linksData.linkData.get(typeId);
if (linkData == null) {
linksData.linkData.putIfAbsent(typeId, new LinkData(ownership, aggregate));
linkData = linksData.linkData.get(typeId);
}
assert linkData != null;
linkOwnerUuid = linkOwnerUuid != null ? linkOwnerUuid : NO_OWNER_UUID;
LinkProperty linkProperty = linkData.uuids.get(linkUuid);
if (linkProperty == null) {
if (linkData.uuids.putIfAbsent(linkUuid, new LinkProperty(linkOwnerUuid)) == null) {
linksData.count.incrementAndGet();
}
linkProperty = linkData.uuids.get(linkUuid);
}
linkProperty.inactive = inactive;
}
private List<Object> selectRawLinksData(Filter filter) {
final GroupDataParam param = new GroupDataParam();
param.addKey("linkType.id");
param.addKey("parentUuid");
param.addKey("parentOwnerUuid");
param.addKey("childUuid");
param.addKey("childOwnerUuid");
param.addKey("inactive");
OrderDataParam order = new OrderDataParam();
order.addProperty(new EntityAttribute("inactive"), OrderDataParam.OrderDirection.DESC, false);
final List<Object> data = session.groupData(Link.class, filter, new QueryParams(order), param);
final HashSet<Long> typeIds = new HashSet<>();
for (Object o : data) {
Object[] row = (Object[]) o;
final Long typeId = (Long) row[0];
if (!typeIds.contains(typeId) && needRefreshLinkType(linkTypes.get(typeId))) {
typeIds.add(typeId);
}
}
refreshLinkTypes(new ArrayList<>(typeIds));
return data;
}
private long selectLinks(String uuid, LinksData linksData) {
final List<Object> data = selectRawLinksData(new Filter(or(
eq(new EntityAttribute("parentUuid"), uuid),
eq(new EntityAttribute("parentOwnerUuid"), uuid),
eq(new EntityAttribute("childUuid"), uuid),
eq(new EntityAttribute("childOwnerUuid"), uuid)
)));
for (Object o : data) {
final Object[] row = (Object[]) o;
final Long typeId = (Long) row[0];
final boolean inactive = row[5] != null && Boolean.TRUE.equals(row[5]);
if (uuid.equals(row[1])) {
refreshLink(linksData, typeId, Ownership.PARENT, false, (String) row[3], (String) row[4], inactive);
}
if (uuid.equals(row[2]) && linkTypes.get(typeId).aggregate) {
refreshLink(linksData, typeId, Ownership.PARENT, true, (String) row[3], (String) row[4], inactive);
}
if (uuid.equals(row[3])) {
refreshLink(linksData, typeId, Ownership.CHILD, false, (String) row[1], (String) row[2], inactive);
}
if (uuid.equals(row[4]) && linkTypes.get(typeId).aggregate) {
refreshLink(linksData, typeId, Ownership.CHILD, true, (String) row[1], (String) row[2], inactive);
}
}
return data.size();
}
private LinksData getLinksData(final String uuid) {
LinksData linksData = links.get(uuid);
if (linksData != null) {
actualizeLinkTypes(linksData);
} else {
linksData = links.getAdvancedCache().computeIfAbsent(uuid, s -> new LinksData());
selectLinks(uuid, linksData);
}
return linksData;
}
@Override
public boolean hasAnyLinkTypes(ClassInfo modelInfo) {
final String modelName = modelInfo.getName();
return domains.isDomainClass(modelName) && selectLinkTypeCount(new Filter(or(
eq(getSequenceInstance("parentDomain.className"), modelName),
eq(getSequenceInstance("childDomain.className"), modelName)
))) > 0;
}
@Override
public boolean hasAnyLinkTypes(ClassInfo modelInfo, String propertyName) {
final String modelName = modelInfo.getName();
return domains.isDomainClass(modelName) && selectLinkTypeCount(new Filter(or(
and(
eq(getSequenceInstance("parentDomain.className"), modelName),
propertyName != null
? eq(getSequenceInstance("parentProperty"), propertyName)
: TRUE
),
and(
eq(getSequenceInstance("childDomain.className"), modelName),
propertyName != null
? eq(getSequenceInstance("childProperty"), propertyName)
: TRUE
)
))) > 0;
}
@Override
public boolean hasAnyLinks(String uuid) {
final LinksData linksData = getLinksData(uuid);
return linksData != null && linksData.count() > 0;
}
@Override
public boolean hasParentLinks(String uuid) {
final LinksData linksData = getLinksData(uuid);
for (LinkData linkData : linksData.linkData.values()) {
if (linkData.ownership == Ownership.CHILD && !linkData.uuids.isEmpty())
return true;
}
return false;
}
@Override
public List<DocumentLinks> linksFor(String uuid) {
return getLinksData(uuid).documentLinks(true);
}
@Override
public List<DocumentLinks> activeLinksFor(String uuid) {
return getLinksData(uuid).documentLinks(false);
}
@Override
public void clear(String uuid, String ownerUuid) {
links.evict(uuid);
if (ownerUuid != null)
links.evict(ownerUuid);
}
@Override
public void create(String clsId, String parentUuid, String parentOwnerUuid, String childUuid,
String childOwnerUuid) {
LinkType linkType = session.getSingleObject(LinkType.class, Filter.byPropertyEquals("clsId", clsId), null);
Link link = session.instantiate(Link.class);
link.setLinkType(linkType);
link.setParentUuid(parentUuid);
link.setParentOwnerUuid(parentOwnerUuid);
link.setChildUuid(childUuid);
link.setChildOwnerUuid(childOwnerUuid);
link.setInactive(false);
session.addNew(link);
session.commit();
Long typeId = linkType.getId();
refreshLinkTypes(asList(typeId));
clear(parentUuid, parentOwnerUuid);
clear(childUuid, childOwnerUuid);
saveChanges(Stream.of(parentUuid, parentOwnerUuid, childUuid, childOwnerUuid)
.filter(Objects::nonNull)
.collect(Collectors.toList())
);
}
@Override
public void inactivateParent(String uuid) {
List<Link> childLinks = session.getObjects(Link.class, new Filter(or(
eq(getSequenceInstance("childUuid"), uuid),
eq(getSequenceInstance("childOwnerUuid"), uuid)
)), null);
for (Link link : childLinks) {
link.setInactive(true);
}
session.commit();
Set<String> changes = new HashSet<>();
for (Link link : childLinks) {
changes.add(link.getParentUuid());
changes.add(link.getChildUuid());
links.evict(link.getParentUuid());
links.evict(link.getChildUuid());
}
saveChanges(changes);
}
@Override
public Set<DocumentOperationEnum> operations(String uuid) {
return getLinksData(uuid).operations();
}
@Override
public Set<String> whiteList(String uuid) {
return getLinksData(uuid).whiteList(null);
}
@Override
public Set<String> whiteList(String uuid, String path) {
return getLinksData(uuid).whiteList(path);
}
@Override
public void invalidateLinkType(long typeId) {
LinkTypeData metaLink = linkTypes.get(typeId);
if (metaLink != null) {
metaLink.lastUpdate = 0;
}
}
private Transaction getTransaction() {
TransactionManager transactionManager = JtaUtils.getTransactionManager();
try {
return transactionManager.getTransaction();
} catch (SystemException e) {
return null;
}
}
private synchronized void saveChanges(Collection<String> uuids) {
Transaction transaction = getTransaction();
if (transaction == null) {
return;
}
ChangeStore store = registeredStores.get(transaction);
if (store == null) {
store = new ChangeStore();
try {
transaction.registerSynchronization(store);
registeredStores.put(transaction, store);
} catch (Exception e) {
return;
}
}
store.changeSet.addAll(uuids);
}
private static class LinkTypeData {
volatile long lastUpdate = 0L;
String code;
String name;
String parentDomain;
String parentProperty;
String childDomain;
String childProperty;
String parentJumpMessage;
String childJumpMessage;
boolean aggregate;
Set<DocumentOperationEnum> parentOperations;
Set<DocumentOperationEnum> childOperations;
Map<String, List<String>> parentWhiteList;
Map<String, List<String>> childWhiteList;
synchronized MetaLink metaLink() {
return new MetaLink(code, name, parentDomain, parentProperty, childDomain, childProperty, parentJumpMessage,
childJumpMessage);
}
}
private static class LinksData {
final AtomicLong count = new AtomicLong(0);
final ConcurrentHashMap<Long, LinkData> linkData = new ConcurrentHashMap<>();
long count() {
return count.get();
}
List<DocumentLinks> documentLinks(boolean inactive) {
ArrayList<DocumentLinks> result = new ArrayList<>();
for (Map.Entry<Long, LinkData> entry : linkData.entrySet()) {
LinkTypeData linkType = linkTypes.get(entry.getKey());
assert linkType != null;
List<String[]> uuids = new ArrayList<>();
List<String[]> inactiveUuids = new ArrayList<>();
for (Map.Entry<String, LinkProperty> a : entry.getValue().uuids.entrySet()) {
if (a.getValue().inactive) {
if (inactive) inactiveUuids.add(new String[]{a.getKey(), a.getValue().owner});
} else {
uuids.add(new String[]{a.getKey(), a.getValue().owner});
}
}
if (uuids.isEmpty() && inactiveUuids.isEmpty()) continue;
result.add(new DocumentLinks(
linkType.metaLink(),
entry.getValue().ownership,
uuids,
inactiveUuids
));
}
return result;
}
Set<DocumentOperationEnum> operations() {
HashSet<DocumentOperationEnum> result = new HashSet<>(asList(DocumentOperationEnum.values()));
for (Map.Entry<Long, LinkData> entry : linkData.entrySet()) {
LinkTypeData linkType = linkTypes.get(entry.getKey());
assert linkType != null;
LinkData linkData = entry.getValue();
switch (linkData.ownership) {
case PARENT:
if (hasActiveLinks(linkData)) {
result.retainAll(linkType.parentOperations);
}
break;
case CHILD:
if (!linkData.uuids.isEmpty()) {
result.retainAll(linkType.childOperations);
}
break;
default:
throw new IllegalStateException("неизвестный тип принадлежности связи " + linkData.ownership);
}
}
return result;
}
public Set<String> whiteList(String path) {
HashSet<String> result = null;
for (Map.Entry<Long, LinkData> entry : linkData.entrySet()) {
LinkTypeData linkType = linkTypes.get(entry.getKey());
assert linkType != null;
LinkData linkData = entry.getValue();
List<String> whiteList = null;
switch (linkData.ownership) {
case PARENT:
if (hasActiveLinks(linkData)) {
if (linkType.aggregate) {
whiteList = linkType.parentWhiteList.get(path);
} else {
whiteList = linkType.parentWhiteList.get(path == null ? linkType.parentProperty : path);
}
}
break;
case CHILD:
if (!linkData.uuids.isEmpty()) {
if (linkType.aggregate) {
whiteList = linkType.childWhiteList.get(path);
} else {
whiteList = linkType.childWhiteList.get(path == null ? linkType.childProperty : path);
}
}
break;
default:
throw new IllegalStateException("неизвестный тип принадлежности связи " + linkData.ownership);
}
if (whiteList == null || whiteList.isEmpty()) return Collections.emptySet();
if (result == null) {
result = new HashSet<>(whiteList);
} else {
result.retainAll(whiteList);
if (result.isEmpty()) return Collections.emptySet();
}
}
return result == null ? Collections.<String>emptySet() : result;
}
private boolean hasActiveLinks(LinkData linkData) {
for (LinkProperty property : linkData.uuids.values()) {
if (!property.inactive) return true;
}
return false;
}
}
private static class LinkData implements Serializable {
final Ownership ownership;
final boolean aggregate;
final ConcurrentHashMap<String, LinkProperty> uuids = new ConcurrentHashMap<>();
LinkData(Ownership ownership, boolean aggregate) {
this.ownership = ownership;
this.aggregate = aggregate;
}
}
private static class LinkProperty {
final String owner;
volatile boolean inactive;
public LinkProperty(String owner) {
this.owner = owner;
}
}
private class ChangeStore implements Synchronization {
private final Set<String> changeSet = new HashSet<>();
@Override
public void beforeCompletion() {
}
@Override
public void afterCompletion(int status) {
if (status == Status.STATUS_ROLLEDBACK) {
for(String change: changeSet){
links.evict(change);
}
}
changeSet.clear();
}
}
}