> 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 extends List extends T>> 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 super IModel> action) {
+ checkLoad();
+ models.forEach(action);
+ }
+
+ /**
+ * Применение действия к каждому объекту.
+ *
+ * @param action действие.
+ */
+ public void forEachObject(Consumer super T> 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