diff --git a/pom.xml b/pom.xml index 74262f4..54ae63b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ru.simplex_software wicket-springdata - 1.5 + 1.7.7 wicket-springdata http://www.simplex-software.ru @@ -74,6 +74,20 @@ spring-context 5.0.5.RELEASE + + + + javax.annotation + javax.annotation-api + 1.3.1 + + + + javax.xml.bind + jaxb-api + 2.4.0-b180725.0427 + + diff --git a/src/main/java/ru/simplex_software/wicket_springdata/HibernateListModel.java b/src/main/java/ru/simplex_software/wicket_springdata/HibernateListModel.java new file mode 100644 index 0000000..cc966b5 --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/HibernateListModel.java @@ -0,0 +1,219 @@ +package ru.simplex_software.wicket_springdata; + +import org.apache.wicket.model.IModel; +import org.springframework.data.domain.Persistable; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * Модель списка, элементы которого обернуты в модели. + * + * @param

тип элементов списка. + */ +public class HibernateListModel

> implements IModel> { + + /** + * Список моделей элементов. + */ + private List> modelList; + + /** + * Загружена ли модель. + */ + private boolean attached; + + /** + * Создание новой модели списка из итерируемого объекта. + * + * @param iterable итерируемый объект. + */ + public HibernateListModel(Iterable

iterable) { + this(StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList())); + } + + /** + * Создание новой модели списка. + */ + public HibernateListModel() { + this(new ArrayList<>()); + } + + /** + * Создание новой модели списка из списка. + * + * @param list список элементов. + */ + public HibernateListModel(List

list) { + this.modelList = wrap(list); + attached = true; + } + + /** + * Получение списка элементов. + */ + @Override + public List

getObject() { + checkLoad(); + return unwrap(modelList); + } + + /** + * Замена списка моделей. + * + * @param list список объектов. + */ + @Override + public void setObject(List

list) { + checkLoad(); + modelList = wrap(list); + } + + /** + * Открепление модели. + */ + @Override + public void detach() { + modelList.forEach(IModel::detach); + attached = false; + } + + /** + * Добавление модели элемента в список. + * + * @param model модель элемента. + * @return результат операции. + */ + public boolean add(IModel

model) { + return add(model.getObject()); + } + + /** + * Добавление элемента в список. + * + * @param item элемент. + * @return результат операции. + */ + public boolean add(P item) { + checkLoad(); + return modelList.add(new HibernateModel<>(item)); + } + + /** + * Удаление модели элемента из списока. + * + * @return результат операции. + */ + public boolean remove(IModel

model) { + return remove(model.getObject()); + } + + /** + * Удаление элемента из списока. + * + * @return результат операции. + */ + public boolean remove(P item) { + checkLoad(); + return modelList.remove(new HibernateModel<>(item)); + } + + /** + * Удаление из списка всех элементов которые есть в коллекции. + * + * @param collection колекция элементов. + * @see List#removeAll(Collection) + */ + public void removeAll(Collection

collection) { + checkLoad(); + modelList.removeIf(model -> collection.contains(model.getObject())); + } + + /** + * Удаление из списка всех элементов которых нет в коллекции. + * + * @param collection колекция элементов. + * @see List#retainAll(Collection) + */ + public void retainAll(Collection

collection) { + checkLoad(); + modelList.removeIf(model -> !collection.contains(model.getObject())); + } + + /** + * Получение итератора списка моделей элементов. + */ + public Iterator> getIterator() { + checkLoad(); + return modelList.iterator(); + } + + /** + * Проверка наличия модели в списке. + * + * @param model список моделей. + * @return наличие модели в списке. + */ + public boolean contains(IModel

model) { + checkLoad(); + return modelList.contains(model); + } + + /** + * Проверка наличия элемента в списке. + * + * @param item элемент. + * @return наличие элемента в списке. + */ + public boolean contains(P item) { + return contains(new HibernateModel<>(item)); + } + + /** + * Очистка модели. + */ + public void clear() { + checkLoad(); + modelList.clear(); + } + + /** + * Оборачивание элементов списка в модели. + * + * @param list список элементов. + * @return список моделей элементов. + */ + private List> wrap(List

list) { + return list.stream() + .map(HibernateModel::new) + .map(model -> (IModel

) model) + .collect(Collectors.toList()); + } + + /** + * Получение списка элементов. + * + * @param modelList список моделей элементов. + * @return список элементов. + */ + private List

unwrap(List> modelList) { + return modelList.stream() + .map(IModel::getObject) + .collect(Collectors.toList()); + } + + /** + * Проверка загрузки и загрузка при необходимости. + */ + private void checkLoad() { + if (!attached) { + modelList.forEach(model -> ((HibernateModel

) model).load()); + attached = true; + } + } +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/HibernateModel.java b/src/main/java/ru/simplex_software/wicket_springdata/HibernateModel.java index b929d9b..64bf199 100644 --- a/src/main/java/ru/simplex_software/wicket_springdata/HibernateModel.java +++ b/src/main/java/ru/simplex_software/wicket_springdata/HibernateModel.java @@ -126,7 +126,13 @@ HibernateModel that = (HibernateModel) o; - if (!id.equals(that.id)) return false; + if (that.id == null) { + that.onDetach(); + } + + if (!id.equals(that.id)) { + return false; + } return tClass.equals(that.tClass); } diff --git a/src/main/java/ru/simplex_software/wicket_springdata/PersistableChoiceRenderer.java b/src/main/java/ru/simplex_software/wicket_springdata/PersistableChoiceRenderer.java new file mode 100644 index 0000000..56e1dcd --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/PersistableChoiceRenderer.java @@ -0,0 +1,48 @@ +package ru.simplex_software.wicket_springdata; + +import org.apache.wicket.markup.html.form.IChoiceRenderer; +import org.apache.wicket.model.IModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Persistable; +import ru.simplex_software.wicket_springdata.functions.SerializableFunction; + +import java.io.Serializable; +import java.util.List; + +/** + * Отрисовщик элементов. + */ +public class PersistableChoiceRenderer> implements IChoiceRenderer { + private static final Logger LOG = LoggerFactory.getLogger(PersistableChoiceRenderer.class); + + /** + * Функция которая возращает отображаемое значение. + */ + private SerializableFunction function; + + public PersistableChoiceRenderer(SerializableFunction function) { + this.function = function; + } + + @Override + public Object getDisplayValue(T object) { + return function.apply(object); + } + + @Override + public String getIdValue(T object, int index) { + return object.getId().toString(); + } + + @Override + public T getObject(String id, IModel> choices) { + for (T persistable : choices.getObject()) { + if (id.equals(persistable.getId().toString())) { + return persistable; + } + } + LOG.warn("Persistable entity with id = {} not found.", id); + return null; + } +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/functions/BiHandlerWrapper.java b/src/main/java/ru/simplex_software/wicket_springdata/functions/BiHandlerWrapper.java new file mode 100644 index 0000000..1cbc8dd --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/functions/BiHandlerWrapper.java @@ -0,0 +1,39 @@ +package ru.simplex_software.wicket_springdata.functions; + +import java.io.Serializable; + +/** + * Обертка над операцией выполняемой с 2 аргументами. + * Проверяет обработчик на null при вызове. + * + * @param тип 1 аргумента. + * @param тип 2 аргумента. + */ +public class BiHandlerWrapper implements Serializable { + + /** + * Обработчик. + */ + private SerializableBiConsumer handler; + + /** + * Установка обработчика. + * + * @param handler обработчик. + */ + public void setHandler(SerializableBiConsumer handler) { + this.handler = handler; + } + + /** + * Вызов обработчика. + * + * @param tData 1 аргумент. + * @param vData 2 аргумент. + */ + public void fire(T tData, V vData) { + if (handler != null) { + handler.accept(tData, vData); + } + } +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/functions/HandlerWrapper.java b/src/main/java/ru/simplex_software/wicket_springdata/functions/HandlerWrapper.java new file mode 100644 index 0000000..3ba9d8a --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/functions/HandlerWrapper.java @@ -0,0 +1,37 @@ +package ru.simplex_software.wicket_springdata.functions; + +import java.io.Serializable; + +/** + * Обертка над операцией. + * Проверяет обработчик на null при вызове. + * + * @param тип аргумента. + */ +public class HandlerWrapper implements Serializable { + + /** + * Обработчик. + */ + private SerializableConsumer handler; + + /** + * Установка обработчика. + * + * @param handler обработчик. + */ + public void setHandler(SerializableConsumer handler) { + this.handler = handler; + } + + /** + * Вызов обработчика. + * + * @param tData аргумент. + */ + public void fire(T tData) { + if (handler != null) { + handler.accept(tData); + } + } +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableBiConsumer.java b/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableBiConsumer.java new file mode 100644 index 0000000..7206a35 --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableBiConsumer.java @@ -0,0 +1,15 @@ +package ru.simplex_software.wicket_springdata.functions; + +import java.io.Serializable; +import java.util.function.BiConsumer; + +/** + * Представляет сериализуемую операцию выполняемую с 2 аргументами. + * + * @param тип 1 аргумента. + * @param тип 2 аргумента. + * @see BiConsumer + */ +@FunctionalInterface +public interface SerializableBiConsumer extends BiConsumer, Serializable { +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableConsumer.java b/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableConsumer.java new file mode 100644 index 0000000..c95940e --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableConsumer.java @@ -0,0 +1,14 @@ +package ru.simplex_software.wicket_springdata.functions; + +import java.io.Serializable; +import java.util.function.Consumer; + +/** + * Представляет сериализуемую операцию. + * + * @param тип аргумента. + * @see Consumer + */ +@FunctionalInterface +public interface SerializableConsumer extends Consumer, Serializable { +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableFunction.java b/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableFunction.java new file mode 100644 index 0000000..5784586 --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableFunction.java @@ -0,0 +1,15 @@ +package ru.simplex_software.wicket_springdata.functions; + +import java.io.Serializable; +import java.util.function.Function; + +/** + * Представляет сериализуемую функцию. + * + * @param тип аргумента. + * @param тип возвращаемого значения. + * @see java.util.function.Function + */ +@FunctionalInterface +public interface SerializableFunction extends Function, Serializable { +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableTriConsumer.java b/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableTriConsumer.java new file mode 100644 index 0000000..660c421 --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/functions/SerializableTriConsumer.java @@ -0,0 +1,15 @@ +package ru.simplex_software.wicket_springdata.functions; + +import java.io.Serializable; + +/** + * Представляет сериализуемую операцию выполняемую с 3 аргументами. + * + * @param тип 1 аргумента. + * @param тип 2 аргумента. + * @param тип 3 аргумента. + * @see ru.simplex_software.wicket_springdata.functions.TriConsumer + */ +@FunctionalInterface +public interface SerializableTriConsumer extends TriConsumer, Serializable { +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/functions/TriConsumer.java b/src/main/java/ru/simplex_software/wicket_springdata/functions/TriConsumer.java new file mode 100644 index 0000000..4f533ad --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/functions/TriConsumer.java @@ -0,0 +1,21 @@ +package ru.simplex_software.wicket_springdata.functions; + +/** + * Представляет операцию выполняемую с аргументами. + * + * @param тип 1 аргумента. + * @param тип 2 аргумента. + * @param тип 3 аргумента. + */ +@FunctionalInterface +public interface TriConsumer { + + /** + * Выполнение операции. + * + * @param t 1 аргумент. + * @param u 2 аргумент. + * @param k 3 аргумент. + */ + void accept(T t, U u, K k); +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/functions/TriHandlerWrapper.java b/src/main/java/ru/simplex_software/wicket_springdata/functions/TriHandlerWrapper.java new file mode 100644 index 0000000..e303039 --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/functions/TriHandlerWrapper.java @@ -0,0 +1,41 @@ +package ru.simplex_software.wicket_springdata.functions; + +import java.io.Serializable; + +/** + * Обертка над операцией выполняемой с 3 аргументами. + * Проверяет обработчик на null при вызове. + * + * @param тип 1 аргумента. + * @param тип 2 аргумента. + * @param тип 3 аргумента. + */ +public class TriHandlerWrapper implements Serializable { + + /** + * Обработчик. + */ + private SerializableTriConsumer handler; + + /** + * Установка обработчика. + * + * @param handler обработчик. + */ + public void setHandler(SerializableTriConsumer handler) { + this.handler = handler; + } + + /** + * Вызов обработчика. + * + * @param tData 1 аргумент. + * @param vData 2 аргумент. + * @param kData 3 аргумент. + */ + public void fire(T tData, V vData, K kData) { + if (handler != null) { + handler.accept(tData, vData, kData); + } + } +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/kryo/AbstractKryoHolder.java b/src/main/java/ru/simplex_software/wicket_springdata/kryo/AbstractKryoHolder.java index 4bf94f2..8cd3916 100644 --- a/src/main/java/ru/simplex_software/wicket_springdata/kryo/AbstractKryoHolder.java +++ b/src/main/java/ru/simplex_software/wicket_springdata/kryo/AbstractKryoHolder.java @@ -4,6 +4,7 @@ import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; import org.springframework.data.domain.Persistable; +import org.springframework.data.repository.CrudRepository; import java.io.Serializable; @@ -27,4 +28,19 @@ public

> P readObject(Input input, Class

type) { return kryo.readObject(input, type); } + + /** + * Регистрация сериалайзера для сохраняемой сущноси. + * + * @param entityClass класс сувщности. + * @param repository репозиторий сущности. + * @param idClass класс первичного ключа сущности. + * @param

тип сущности. + * @param тип первичного ключа сущности. + */ + protected

, ID extends Serializable> void registerPersistable(Class

entityClass, + CrudRepository repository, + Class idClass) { + kryo.register(entityClass, new PersistableSerializer<>(repository::findById, idClass)); + } } \ No newline at end of file diff --git a/src/main/java/ru/simplex_software/wicket_springdata/kryo/KryoListModel.java b/src/main/java/ru/simplex_software/wicket_springdata/kryo/KryoListModel.java new file mode 100644 index 0000000..e952a87 --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/kryo/KryoListModel.java @@ -0,0 +1,227 @@ +package ru.simplex_software.wicket_springdata.kryo; + +import org.apache.wicket.model.IModel; +import org.springframework.data.domain.Persistable; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +/** + * Модель списка с Kryo моделями. + * + * @param тип элементов. + */ +public class KryoListModel> implements IModel> { + + /** + * Список моделей объектов. + */ + private final List> models = new ArrayList<>(); + + /** + * Загружена ли модель. + */ + private boolean attached; + + /** + * Создание новой модели списка. + * + * @param list список элементов. + */ + public KryoListModel(List list) { + wrap(list); + attached = true; + } + + /** + * Пуст ли список. + */ + public boolean isEmpty() { + checkLoad(); + return models.isEmpty(); + } + + /** + * Получение размера списка. + */ + public int size() { + checkLoad(); + return models.size(); + } + + /** + * Добавление объекта. + * + * @param object объект. + */ + public boolean add(T object) { + return add(new KryoModel<>(object)); + } + + /** + * Добавление объекта по индексу. + * + * @param index индекс. + * @param object объект. + */ + public void add(int index, T object) { + add(index, new KryoModel<>(object)); + } + + /** + * Добавление модели. + * + * @param model модель. + */ + public boolean add(IModel model) { + checkLoad(); + return models.add(model); + } + + /** + * Добавление модели по индексу. + * + * @param index индекс. + * @param model модель. + */ + public void add(int index, IModel model) { + checkLoad(); + models.add(index, model); + } + + /** + * Удаление модели из списка. + * + * @param model модель. + * @return если элемент найден в списке. + */ + public boolean remove(IModel model) { + checkLoad(); + return models.remove(model); + } + + /** + * Удаление модели из списка по индексу. + * + * @param index индекс. + * @return удаленная модель. + */ + public IModel remove(int index) { + checkLoad(); + return models.remove(index); + } + + /** + * Получение объекта по индексу. + * + * @param index индекс. + * @return объект по индексу. + */ + public T getObjectAt(int index) { + return getModelAt(index).getObject(); + } + + /** + * Получение модели по индексу. + * + * @param index индекс. + * @return иодель по индексу. + */ + public IModel getModelAt(int index) { + checkLoad(); + return models.get(index); + } + + /** + * Получение итератора моделей. + */ + public Iterator> iterator() { + checkLoad(); + return models.iterator(); + } + + /** + * Применение действия к каждой модели. + * + * @param action действие. + */ + public void forEachModel(Consumer> action) { + checkLoad(); + models.forEach(action); + } + + /** + * Применение действия к каждому объекту. + * + * @param action действие. + */ + public void forEachObject(Consumer action) { + checkLoad(); + models.stream() + .map(IModel::getObject) + .forEach(action); + } + + /** + * Очистка списка. + */ + public void clear() { + checkLoad(); + models.clear(); + } + + /** + * Получение списка объектов. + */ + @Override + public List getObject() { + checkLoad(); + return models.stream() + .map(IModel::getObject) + .collect(Collectors.toList()); + } + + /** + * Установка списка объектов в модель. + * + * @param list список объектов. + */ + @Override + public void setObject(List list) { + wrap(list); + } + + /** + * Открепление модели. + */ + @Override + public void detach() { + checkLoad(); + models.forEach(IModel::detach); + attached = false; + } + + /** + * Проверка загрузки и загрузка при необходимости. + */ + private void checkLoad() { + if (!attached) { + models.forEach(model -> ((KryoModel) model).load()); + } + attached = true; + } + + /** + * Оборачивание элементов списка в модели. + * + * @param list список. + */ + private void wrap(List list) { + models.clear(); + list.stream().map(KryoModel::new).forEach(models::add); + } +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/kryo/KryoMapModel.java b/src/main/java/ru/simplex_software/wicket_springdata/kryo/KryoMapModel.java new file mode 100644 index 0000000..9606434 --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/kryo/KryoMapModel.java @@ -0,0 +1,169 @@ +package ru.simplex_software.wicket_springdata.kryo; + +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.springframework.data.domain.Persistable; + +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Модель отображения. + * + * @param тип ключа, сохраняемый в БД. + * @param тип значения. + */ +@Deprecated +public class KryoMapModel, V extends Serializable> implements IModel> { + + private Map, IModel> modelMap; + + /** + * Загружена ли модель. + */ + private boolean attached; + + /** + * Создание новой модели отображеия. + * + * @param map исходное отображение. + */ + public KryoMapModel(Map map) { + this.modelMap = wrap(map); + attached = true; + } + + /** + * Получение значения из модели. + */ + @Override + public Map getObject() { + checkLoad(); + return unwrap(modelMap); + } + + /** + * Открепление модели. + */ + @Override + public void detach() { + modelMap.keySet().forEach(IModel::detach); + modelMap.values().forEach(IModel::detach); + attached = false; + } + + /** + * Получение количества элементов. + */ + public int size() { + return modelMap.size(); + } + + /** + * Добавление ключа и значения. + * + * @param key ключ. + * @param value значение. + */ + public void put(K key, V value) { + put(new KryoModel<>(key), Model.of(value)); + } + + /** + * Добавление моделей ключа и значения. + * + * @param key модель ключа. + * @param value модель значения. + */ + public void put(IModel key, IModel value) { + checkLoad(); + modelMap.put(key, value); + } + + /** + * Получение значения по модели ключа. + * + * @param keyModel модель ключа. + * @return значение. + */ + public V get(IModel keyModel) { + checkLoad(); + return modelMap.get(keyModel).getObject(); + } + + /** + * Удаление ключа и значения по модели ключа. + * + * @param keyModel модель ключа. + */ + public void remove(IModel keyModel) { + modelMap.remove(keyModel); + } + + /** + * Получение итератор ключей. + */ + public Iterator> getKeysIterator() { + checkLoad(); + return modelMap.keySet().iterator(); + } + + /** + * Получение итератора ключей после сортировки. + * + * @param comparator компаратор для сортировки. + * @return итератор ключей. + */ + public Iterator> getSortedModelKeysIterator(Comparator> comparator) { + checkLoad(); + Comparator, IModel>> mapComparator = (me1, me2) -> { + AbstractMap.SimpleEntry e1 = new AbstractMap.SimpleEntry<>(me1.getKey().getObject(), me1.getValue().getObject()); + AbstractMap.SimpleEntry e2 = new AbstractMap.SimpleEntry<>(me2.getKey().getObject(), me2.getValue().getObject()); + return comparator.compare(e1, e2); + }; + return modelMap.entrySet().stream() + .sorted(mapComparator) + .map(Map.Entry::getKey) + .iterator(); + } + + /** + * Проверка загрузки и загрузка при необходимости. + */ + private void checkLoad() { + if (!attached) { + modelMap.keySet().forEach(model -> ((KryoModel) model).load()); + } + attached = true; + } + + /** + * Оборачивание ключей и значений отображения в модели. + * + * @param map отображение. + * @return отображение моделей. + */ + private Map, IModel> wrap(Map map) { + Map, IModel> modelMap = new LinkedHashMap<>(); + map.forEach((key, value) -> modelMap.put(new KryoModel<>(key), Model.of(value))); + return modelMap; + } + + /** + * Получение отображения с ключами и значениями из моделей. + * + * @param modelMap отображение моделей. + * @return отображение ключей и значений. + */ + private Map unwrap(Map, IModel> modelMap) { + return modelMap.entrySet().stream() + .collect(Collectors.toMap( + entry -> entry.getKey().getObject(), + entry -> entry.getValue().getObject())); + } +} diff --git a/src/main/java/ru/simplex_software/wicket_springdata/kryo/KryoModel.java b/src/main/java/ru/simplex_software/wicket_springdata/kryo/KryoModel.java index c1b2136..a9c2ca7 100644 --- a/src/main/java/ru/simplex_software/wicket_springdata/kryo/KryoModel.java +++ b/src/main/java/ru/simplex_software/wicket_springdata/kryo/KryoModel.java @@ -25,23 +25,32 @@ private byte[] data = new byte[0]; private Class

clazz; + public KryoModel() { + inject(); + } + public KryoModel(P entity) { super(entity); inject(); clazz = (Class

) entity.getClass(); } + /** + * Открепление модели. + */ @Override public void onDetach() { - Output output = new Output(new ByteArrayOutputStream()); - kryoHolder.writeObject(output, getObject()); - data = output.toBytes(); + if (clazz != null) { + Output output = new Output(new ByteArrayOutputStream()); + kryoHolder.writeObject(output, getObject()); + data = output.toBytes(); + } } @Override public void setObject(P entity) { super.setObject(entity); - clazz = (Class

) entity.getClass(); + clazz = entity == null ? null : (Class

) entity.getClass(); } /** @@ -57,8 +66,16 @@ .iterator(); } + /** + * Загрузка модели. + * + * @return объект модели. + */ @Override protected P load() { + if (clazz == null) { + return null; + } Input input = new Input(new ByteArrayInputStream(data)); return kryoHolder.readObject(input, clazz); } diff --git a/src/main/java/ru/simplex_software/wicket_springdata/kryo/NewKryoMapModel.java b/src/main/java/ru/simplex_software/wicket_springdata/kryo/NewKryoMapModel.java new file mode 100644 index 0000000..6f683c4 --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/kryo/NewKryoMapModel.java @@ -0,0 +1,170 @@ +package ru.simplex_software.wicket_springdata.kryo; + +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.springframework.data.domain.Persistable; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Модель отображения. + * + * @param тип ключей. + * @param тип значений. + */ +public class NewKryoMapModel implements IModel> { + + private final Map, IModel> modelMap; + + /** + * Загружена ли модель. + */ + private boolean attached; + + /** + * Создание новой модели отображеия. + * + * @param map исходное отображение. + */ + public NewKryoMapModel(Map map) { + this.modelMap = wrap(map); + attached = true; + } + + @Override + public Map getObject() { + checkLoad(); + return unwrap(modelMap); + } + + /** + * Открепление модели. + */ + @Override + public void detach() { + modelMap.keySet().forEach(IModel::detach); + modelMap.values().forEach(IModel::detach); + attached = false; + } + + /** + * Получение значения по модели ключа. + * + * @param keyModel модель ключа. + * @return значение. + */ + public V get(IModel keyModel) { + return getValueModel(keyModel).getObject(); + } + + /** + * Получение модели значения по модели ключа. + * + * @param keyModel модель ключа. + * @return модель значения. + */ + public IModel getValueModel(IModel keyModel) { + checkLoad(); + return modelMap.get(keyModel); + } + + /** + * Получение итератора ключей. + */ + public Iterator> getKeysIterator() { + checkLoad(); + return modelMap.keySet().iterator(); + } + + /** + * Проверка загрузки и загрузка при необходимости. + */ + private void checkLoad() { + if (!attached) { + modelMap.keySet().forEach(this::loadModel); + modelMap.values().forEach(this::loadModel); + } + attached = true; + } + + /** + * Загрузка модели. + * + * @param model модель. + */ + private void loadModel(IModel model) { + if (model instanceof KryoModel) { + ((KryoModel) model).load(); + } + } + + /** + * Оборачивание ключа в модель. + * + * @param key ключ. + * @return модель ключа. + */ + @SuppressWarnings("unchecked") + private IModel wrapKey(K key) { + return (IModel) wrapObject(key); + } + + /** + * Оборачивание значения в модель. + * + * @param value значение. + * @return модель значения. + */ + @SuppressWarnings("unchecked") + private IModel wrapValue(V value) { + return (IModel) wrapObject(value); + } + + /** + * Оборачивание объекта в модель. + * Объект должен реализовывать интерфейс {@link Persistable} или {@link Serializable}. + * + * @param object объект. + * @return модель объекта. + * @throws IllegalArgumentException если объект не {@link Persistable} и не {@link Serializable}. + * @see Persistable + * @see Serializable + */ + @SuppressWarnings("unchecked") + private IModel wrapObject(Object object) throws IllegalArgumentException { + if (object instanceof Persistable) { + return new KryoModel<>((Persistable) object); + } else if (object instanceof Serializable) { + return Model.of((Serializable) object); + } else throw new IllegalArgumentException("object must be Persistable or Serializable"); + } + + /** + * Оборачивание ключей и значений отображения в модели. + * + * @param map отображение. + * @return отображение моделей. + */ + private Map, IModel> wrap(Map map) { + Map, IModel> modelMap = new LinkedHashMap<>(); + map.forEach((key, value) -> modelMap.put(wrapKey(key), wrapValue(value))); + return modelMap; + } + + /** + * Получение отображения с ключами и значениями из моделей. + * + * @param modelMap отображение моделей. + * @return отображение ключей и значений. + */ + private Map unwrap(Map, IModel> modelMap) { + return modelMap.entrySet().stream() + .collect(Collectors.toMap( + entry -> entry.getKey().getObject(), + entry -> entry.getValue().getObject())); + } +} \ No newline at end of file diff --git a/src/main/java/ru/simplex_software/wicket_springdata/kryo/PersistableSerializer.java b/src/main/java/ru/simplex_software/wicket_springdata/kryo/PersistableSerializer.java index d827708..c1cbb42 100644 --- a/src/main/java/ru/simplex_software/wicket_springdata/kryo/PersistableSerializer.java +++ b/src/main/java/ru/simplex_software/wicket_springdata/kryo/PersistableSerializer.java @@ -15,11 +15,17 @@ */ public class PersistableSerializer

, ID extends Serializable> extends Serializer

{ - private Function> findByIsFunction; - private Class idClass; + private final Function> findByIdFunction; + private final Class idClass; - public PersistableSerializer(Function> findByIsFunction, Class idClass) { - this.findByIsFunction = findByIsFunction; + /** + * Созания сериализатора для хранимых сущностей. + * + * @param findByIdFunction функция поиска сущности по первичному ключу. + * @param idClass класс первичного ключа сущности. + */ + public PersistableSerializer(Function> findByIdFunction, Class idClass) { + this.findByIdFunction = findByIdFunction; this.idClass = idClass; } @@ -43,7 +49,8 @@ } else { Serializer idSerializer = kryo.getSerializer(idClass); ID id = idSerializer.read(kryo, input, idClass); - return findByIsFunction.apply(id).get(); + return findByIdFunction.apply(id) + .orElseThrow(() -> new IllegalArgumentException("Cannot load entity of type " + type + " with id " + id)); } } } diff --git a/src/main/java/ru/simplex_software/wicket_springdata/util/FileUtil.java b/src/main/java/ru/simplex_software/wicket_springdata/util/FileUtil.java new file mode 100644 index 0000000..458d444 --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/util/FileUtil.java @@ -0,0 +1,90 @@ +package ru.simplex_software.wicket_springdata.util; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Вспомогательный класс для работы с файлами. + */ +public class FileUtil { + public static Logger LOG = LoggerFactory.getLogger(FileUtil.class); + + /** + * Получение файла по имени. + * + * @param filename имя файла. + * @return файл. + */ + public static File getFile(String filename, String FOLDER) { + return new File(FOLDER + filename); + } + + /** + * Сохранение файла. + * + * @param filename имя файла. + * @param data данные файла. + */ + public static void saveFile(String filename, byte[] data, String FOLDER) { + try { + File file = new File(FOLDER + filename); + FileUtils.writeByteArrayToFile(file, data); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Получение массива байт из файла. + * + * @param filename имя файла. + * @return массив байт файла. + */ + public static byte[] getFileBytes(String filename, String FOLDER) { + try (InputStream inputStream = new FileInputStream(new File(FOLDER + filename))) { + return IOUtils.toByteArray(inputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Удаление файла. + * + * @param filename имя файла. + */ + public static void deleteFile(String filename, String FOLDER) { + try { + Files.delete(Paths.get(FOLDER + filename)); + } catch (IOException e) { + LOG.warn("Cannot delete file {}: {}", filename, e.getMessage()); + } + } + + /** + * Запись файла в поток. + * + * @param file файл. + * @param outputStream поток для записи. + */ + public static void writeFileToStream(File file, OutputStream outputStream) { + try { + Path path = file.toPath(); + Files.copy(path, outputStream); + outputStream.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/simplex_software/wicket_springdata/util/HashCalculator.java b/src/main/java/ru/simplex_software/wicket_springdata/util/HashCalculator.java new file mode 100644 index 0000000..f36e105 --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/util/HashCalculator.java @@ -0,0 +1,31 @@ +package ru.simplex_software.wicket_springdata.util; + +import javax.xml.bind.DatatypeConverter; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Класс для вычисления хеша. + */ +public class HashCalculator { + + private static MessageDigest messageDigest; + + static { + try { + messageDigest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * Получение SHA-256 хеша. + * + * @param data массив байт. + * @return строка с SHA-256 хешем. + */ + public static String getSHA256String(byte[] data) { + return DatatypeConverter.printHexBinary(messageDigest.digest(data)); + } +} \ No newline at end of file diff --git a/src/main/java/ru/simplex_software/wicket_springdata/util/WicketUrlUtil.java b/src/main/java/ru/simplex_software/wicket_springdata/util/WicketUrlUtil.java new file mode 100644 index 0000000..e76ba0e --- /dev/null +++ b/src/main/java/ru/simplex_software/wicket_springdata/util/WicketUrlUtil.java @@ -0,0 +1,77 @@ +package ru.simplex_software.wicket_springdata.util; + +import org.apache.wicket.core.request.handler.BookmarkablePageRequestHandler; +import org.apache.wicket.core.request.handler.PageProvider; +import org.apache.wicket.request.IRequestHandler; +import org.apache.wicket.request.IRequestMapper; +import org.apache.wicket.request.Url; +import org.apache.wicket.request.component.IRequestablePage; +import org.apache.wicket.request.cycle.RequestCycle; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Вспомогательный класс для работы с URL. + */ +public final class WicketUrlUtil { + + public static final Logger LOG = LoggerFactory.getLogger(WicketUrlUtil.class); + + private WicketUrlUtil() { + throw new IllegalStateException(""); + } + + /** + * Создание URL для страницы с параметрами. + * + * @param pageClass класс страницы. + * @param parameters параметры. + * @return URL страницы с параметрами. + */ + public static String createPageWithParametersUrl(Class pageClass, + IRequestMapper mapper, PageParameters parameters) { + Url url = RequestCycle.get().getRequest().getUrl(); + String pageUrl = getPageUrl(pageClass, mapper); + StringBuilder builder = new StringBuilder(); + + builder.append(url.getProtocol()).append("://").append(url.getHost()); + + String protocol = url.getProtocol(); + Integer port = url.getPort(); + if ((protocol.equals("http") && port.equals(80)) + || (protocol.equals("https") && port.equals(443))) { + builder.append("/").append(pageUrl); + } else { + builder.append(":").append(url.getPort()).append("/").append(pageUrl); + } + + // Логгируем протокол и полный url заявки, из которого excel формирует гипер-ссылку для перехода по ctrl+click + LOG.debug("Protocol: " + url.getProtocol()); + LOG.debug("Link to request: " + builder.toString()); + + // Добавление параметров. + if (!parameters.isEmpty()) { + builder.append("?"); + } + + String prefix = ""; + for (String key : parameters.getNamedKeys()) { + builder.append(prefix).append(key).append("=").append(parameters.get(key)); + prefix = "&"; + } + + return builder.toString(); + } + + /** + * Получение URL страницы по её классу. + * + * @param pageClass класс страницы. + * @return URL страницы. + */ + private static String getPageUrl(Class pageClass, IRequestMapper mapper) { + IRequestHandler handler = new BookmarkablePageRequestHandler(new PageProvider(pageClass)); + return mapper.mapHandler(handler).toString(); + } +}