[New] Added transformation to domain specific object when querying
Now StrolchTransaction.doQuery() returns the result of the transformation done by the StrolchElementVisitor which is configured on the ResourceQuery or OrderQuery
This commit is contained in:
parent
daa68dde24
commit
d9efdc3a31
|
@ -28,9 +28,7 @@ import li.strolch.agent.api.StrolchRealm;
|
|||
import li.strolch.exception.StrolchException;
|
||||
import li.strolch.model.GroupedParameterizedElement;
|
||||
import li.strolch.model.Locator;
|
||||
import li.strolch.model.Order;
|
||||
import li.strolch.model.ParameterBag;
|
||||
import li.strolch.model.Resource;
|
||||
import li.strolch.model.StrolchElement;
|
||||
import li.strolch.model.StrolchRootElement;
|
||||
import li.strolch.model.Tags;
|
||||
|
@ -166,12 +164,12 @@ public abstract class AbstractTransaction implements StrolchTransaction {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<Order> doQuery(OrderQuery query) {
|
||||
public <U> List<U> doQuery(OrderQuery<U> query) {
|
||||
return getPersistenceHandler().getOrderDao(this).doQuery(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Resource> doQuery(ResourceQuery query) {
|
||||
public <U> List<U> doQuery(ResourceQuery<U> query) {
|
||||
return getPersistenceHandler().getResourceDao(this).doQuery(query);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,5 +25,5 @@ import li.strolch.model.query.OrderQuery;
|
|||
*/
|
||||
public interface OrderDao extends StrolchDao<Order> {
|
||||
|
||||
public List<Order> doQuery(OrderQuery query);
|
||||
public <U> List<U> doQuery(OrderQuery<U> query);
|
||||
}
|
||||
|
|
|
@ -25,5 +25,5 @@ import li.strolch.model.query.ResourceQuery;
|
|||
*/
|
||||
public interface ResourceDao extends StrolchDao<Resource> {
|
||||
|
||||
public List<Resource> doQuery(ResourceQuery query);
|
||||
public <U> List<U> doQuery(ResourceQuery<U> query);
|
||||
}
|
||||
|
|
|
@ -64,9 +64,9 @@ public interface StrolchTransaction extends AutoCloseable {
|
|||
|
||||
public void addCommand(Command command);
|
||||
|
||||
public List<Order> doQuery(OrderQuery query);
|
||||
public <U> List<U> doQuery(OrderQuery<U> query);
|
||||
|
||||
public List<Resource> doQuery(ResourceQuery query);
|
||||
public <U> List<U> doQuery(ResourceQuery<U> query);
|
||||
|
||||
/**
|
||||
* <p>
|
||||
|
|
|
@ -11,9 +11,9 @@ import li.strolch.runtime.query.inmemory.InMemoryQuery;
|
|||
public class InMemoryOrderDao extends InMemoryDao<Order> implements OrderDao {
|
||||
|
||||
@Override
|
||||
public List<Order> doQuery(OrderQuery orderQuery) {
|
||||
public <U> List<U> doQuery(OrderQuery<U> orderQuery) {
|
||||
InMemoryOrderQueryVisitor visitor = new InMemoryOrderQueryVisitor();
|
||||
InMemoryQuery<Order> query = visitor.visit(orderQuery);
|
||||
InMemoryQuery<Order, U> query = visitor.visit(orderQuery);
|
||||
return query.doQuery(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ import li.strolch.runtime.query.inmemory.InMemoryResourceQueryVisitor;
|
|||
public class InMemoryResourceDao extends InMemoryDao<Resource> implements ResourceDao {
|
||||
|
||||
@Override
|
||||
public List<Resource> doQuery(ResourceQuery resourceQuery) {
|
||||
public <U> List<U> doQuery(ResourceQuery<U> resourceQuery) {
|
||||
InMemoryResourceQueryVisitor visitor = new InMemoryResourceQueryVisitor();
|
||||
InMemoryQuery<Resource> query = visitor.visit(resourceQuery);
|
||||
InMemoryQuery<Resource, U> query = visitor.visit(resourceQuery);
|
||||
return query.doQuery(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package li.strolch.runtime.query.inmemory;
|
||||
|
||||
import li.strolch.model.Order;
|
||||
import li.strolch.model.OrderVisitor;
|
||||
import li.strolch.model.query.DateSelection;
|
||||
import li.strolch.model.query.OrderQuery;
|
||||
import li.strolch.model.query.OrderQueryVisitor;
|
||||
|
@ -38,7 +39,8 @@ public class InMemoryOrderQueryVisitor extends InMemoryQueryVisitor<Order, Order
|
|||
return new InMemoryOrderQueryVisitor();
|
||||
}
|
||||
|
||||
public InMemoryQuery<Order> visit(OrderQuery orderQuery) {
|
||||
public <U> InMemoryQuery<Order, U> visit(OrderQuery<U> orderQuery) {
|
||||
DBC.PRE.assertNotNull("OrderVisitor may not be null!", orderQuery.getElementVisitor());
|
||||
orderQuery.accept(this);
|
||||
|
||||
if (this.navigator == null) {
|
||||
|
@ -46,11 +48,12 @@ public class InMemoryOrderQueryVisitor extends InMemoryQueryVisitor<Order, Order
|
|||
throw new QueryException(msg);
|
||||
}
|
||||
|
||||
OrderVisitor<U> elementVisitor = orderQuery.getElementVisitor();
|
||||
if (this.selectors.isEmpty())
|
||||
return new InMemoryQuery<>(this.navigator, null);
|
||||
return new InMemoryQuery<>(this.navigator, null, elementVisitor);
|
||||
|
||||
DBC.PRE.assertTrue("Invalid query as it may only contain one selector!", this.selectors.size() == 1); //$NON-NLS-1$
|
||||
return new InMemoryQuery<>(this.navigator, this.selectors.get(0));
|
||||
return new InMemoryQuery<>(this.navigator, this.selectors.get(0), elementVisitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -15,28 +15,33 @@
|
|||
*/
|
||||
package li.strolch.runtime.query.inmemory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import li.strolch.model.StrolchElement;
|
||||
import li.strolch.model.visitor.StrolchElementVisitor;
|
||||
import li.strolch.persistence.api.StrolchDao;
|
||||
import ch.eitchnet.utils.dbc.DBC;
|
||||
|
||||
/**
|
||||
* @author Robert von Burg <eitch@eitchnet.ch>
|
||||
*/
|
||||
public class InMemoryQuery<T extends StrolchElement> {
|
||||
public class InMemoryQuery<T extends StrolchElement, U> {
|
||||
|
||||
private Navigator<T> navigator;
|
||||
private Selector<T> selector;
|
||||
private StrolchElementVisitor<T, U> elementVisitor;
|
||||
|
||||
public InMemoryQuery() {
|
||||
// empty constructor
|
||||
}
|
||||
|
||||
public InMemoryQuery(Navigator<T> navigator, Selector<T> selector) {
|
||||
public InMemoryQuery(Navigator<T> navigator, Selector<T> selector, StrolchElementVisitor<T, U> elementVisitor) {
|
||||
this.navigator = navigator;
|
||||
this.selector = selector;
|
||||
this.elementVisitor = elementVisitor;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,24 +52,39 @@ public class InMemoryQuery<T extends StrolchElement> {
|
|||
this.navigator = navigator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param selector
|
||||
* the selector to set
|
||||
*/
|
||||
public void setSelector(Selector<T> selector) {
|
||||
this.selector = selector;
|
||||
}
|
||||
|
||||
public List<T> doQuery(StrolchDao<T> dao) {
|
||||
/**
|
||||
* @param elementVisitor
|
||||
* the elementVisitor to set
|
||||
*/
|
||||
public void setElementVisitor(StrolchElementVisitor<T, U> elementVisitor) {
|
||||
this.elementVisitor = elementVisitor;
|
||||
}
|
||||
|
||||
public List<U> doQuery(StrolchDao<T> dao) {
|
||||
|
||||
if (this.selector == null)
|
||||
return Collections.emptyList();
|
||||
|
||||
List<U> result = new ArrayList<U>();
|
||||
List<T> elements = this.navigator.navigate(dao);
|
||||
Iterator<T> iter = elements.iterator();
|
||||
while (iter.hasNext()) {
|
||||
T element = iter.next();
|
||||
if (!this.selector.select(element)) {
|
||||
iter.remove();
|
||||
if (this.selector.select(element)) {
|
||||
U returnValue = this.elementVisitor.visit(element);
|
||||
DBC.INTERIM.assertNotNull("Visitor may not return null in query!", returnValue);
|
||||
result.add(returnValue);
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ package li.strolch.runtime.query.inmemory;
|
|||
|
||||
import ch.eitchnet.utils.dbc.DBC;
|
||||
import li.strolch.model.Resource;
|
||||
import li.strolch.model.ResourceVisitor;
|
||||
import li.strolch.model.query.ResourceQuery;
|
||||
import li.strolch.model.query.ResourceQueryVisitor;
|
||||
import li.strolch.model.query.StrolchTypeNavigation;
|
||||
|
@ -37,7 +38,8 @@ public class InMemoryResourceQueryVisitor extends InMemoryQueryVisitor<Resource,
|
|||
return new InMemoryResourceQueryVisitor();
|
||||
}
|
||||
|
||||
public InMemoryQuery<Resource> visit(ResourceQuery resourceQuery) {
|
||||
public <U> InMemoryQuery<Resource, U> visit(ResourceQuery<U> resourceQuery) {
|
||||
DBC.PRE.assertNotNull("ResourceVisitor may not be null!", resourceQuery.getElementVisitor());
|
||||
resourceQuery.accept(this);
|
||||
|
||||
if (this.navigator == null) {
|
||||
|
@ -45,11 +47,12 @@ public class InMemoryResourceQueryVisitor extends InMemoryQueryVisitor<Resource,
|
|||
throw new QueryException(msg);
|
||||
}
|
||||
|
||||
ResourceVisitor<U> elementVisitor = resourceQuery.getElementVisitor();
|
||||
if (this.selectors.isEmpty())
|
||||
return new InMemoryQuery<>(this.navigator, null);
|
||||
return new InMemoryQuery<>(this.navigator, null, elementVisitor);
|
||||
|
||||
DBC.PRE.assertTrue("Invalid query as it may only contain one selector!", this.selectors.size() == 1); //$NON-NLS-1$
|
||||
return new InMemoryQuery<>(this.navigator, this.selectors.get(0));
|
||||
DBC.INTERIM.assertTrue("Invalid query as it may only contain one selector!", this.selectors.size() == 1); //$NON-NLS-1$
|
||||
return new InMemoryQuery<>(this.navigator, this.selectors.get(0), elementVisitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,17 +29,9 @@ import li.strolch.model.State;
|
|||
import li.strolch.model.parameter.BooleanParameter;
|
||||
import li.strolch.model.parameter.FloatParameter;
|
||||
import li.strolch.model.parameter.StringParameter;
|
||||
import li.strolch.model.visitor.NoStrategyVisitor;
|
||||
import li.strolch.persistence.inmemory.InMemoryOrderDao;
|
||||
import li.strolch.persistence.inmemory.InMemoryResourceDao;
|
||||
import li.strolch.runtime.query.inmemory.AndSelector;
|
||||
import li.strolch.runtime.query.inmemory.AnyNavigator;
|
||||
import li.strolch.runtime.query.inmemory.BooleanSelector;
|
||||
import li.strolch.runtime.query.inmemory.IdSelector;
|
||||
import li.strolch.runtime.query.inmemory.InMemoryQuery;
|
||||
import li.strolch.runtime.query.inmemory.NameSelector;
|
||||
import li.strolch.runtime.query.inmemory.OrSelector;
|
||||
import li.strolch.runtime.query.inmemory.ParameterSelector;
|
||||
import li.strolch.runtime.query.inmemory.Selector;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -56,9 +48,10 @@ public class InMemoryQueryTest {
|
|||
InMemoryOrderDao dao = new InMemoryOrderDao();
|
||||
dao.saveAll(orders);
|
||||
|
||||
InMemoryQuery<Order> orderQuery = new InMemoryQuery<>();
|
||||
InMemoryQuery<Order, Order> orderQuery = new InMemoryQuery<>();
|
||||
orderQuery.setNavigator(new AnyNavigator<Order>());
|
||||
orderQuery.setSelector(new IdSelector<Order>("@1"));
|
||||
orderQuery.setElementVisitor(new NoStrategyVisitor<Order>());
|
||||
|
||||
List<Order> result = orderQuery.doQuery(dao);
|
||||
assertEquals(1, result.size());
|
||||
|
@ -72,9 +65,10 @@ public class InMemoryQueryTest {
|
|||
InMemoryResourceDao dao = new InMemoryResourceDao();
|
||||
dao.saveAll(resources);
|
||||
|
||||
InMemoryQuery<Resource> resourceQuery = new InMemoryQuery<>();
|
||||
InMemoryQuery<Resource, Resource> resourceQuery = new InMemoryQuery<>();
|
||||
resourceQuery.setNavigator(new AnyNavigator<Resource>());
|
||||
resourceQuery.setSelector(new IdSelector<Resource>("@1"));
|
||||
resourceQuery.setElementVisitor(new NoStrategyVisitor<Resource>());
|
||||
|
||||
List<Resource> result = resourceQuery.doQuery(dao);
|
||||
assertEquals(1, result.size());
|
||||
|
@ -88,8 +82,9 @@ public class InMemoryQueryTest {
|
|||
InMemoryResourceDao dao = new InMemoryResourceDao();
|
||||
dao.saveAll(resources);
|
||||
|
||||
InMemoryQuery<Resource> resourceQuery = new InMemoryQuery<>();
|
||||
InMemoryQuery<Resource, Resource> resourceQuery = new InMemoryQuery<>();
|
||||
resourceQuery.setNavigator(new AnyNavigator<Resource>());
|
||||
resourceQuery.setElementVisitor(new NoStrategyVisitor<Resource>());
|
||||
BooleanSelector<Resource> andSelector = new OrSelector<>(new IdSelector<Resource>("@3"),
|
||||
new IdSelector<Resource>("@4"));
|
||||
resourceQuery.setSelector(andSelector);
|
||||
|
@ -107,8 +102,9 @@ public class InMemoryQueryTest {
|
|||
InMemoryResourceDao dao = new InMemoryResourceDao();
|
||||
dao.saveAll(resources);
|
||||
|
||||
InMemoryQuery<Resource> resourceQuery = new InMemoryQuery<>();
|
||||
InMemoryQuery<Resource, Resource> resourceQuery = new InMemoryQuery<>();
|
||||
resourceQuery.setNavigator(new AnyNavigator<Resource>());
|
||||
resourceQuery.setElementVisitor(new NoStrategyVisitor<Resource>());
|
||||
List<Selector<Resource>> andSelectors = new ArrayList<>();
|
||||
andSelectors.add(new IdSelector<Resource>("@3"));
|
||||
andSelectors.add(new NameSelector<Resource>("Res 3"));
|
||||
|
@ -127,8 +123,9 @@ public class InMemoryQueryTest {
|
|||
InMemoryResourceDao dao = new InMemoryResourceDao();
|
||||
dao.saveAll(resources);
|
||||
|
||||
InMemoryQuery<Resource> resourceQuery = new InMemoryQuery<>();
|
||||
InMemoryQuery<Resource, Resource> resourceQuery = new InMemoryQuery<>();
|
||||
resourceQuery.setNavigator(new AnyNavigator<Resource>());
|
||||
resourceQuery.setElementVisitor(new NoStrategyVisitor<Resource>());
|
||||
List<Selector<Resource>> andSelectors = new ArrayList<>();
|
||||
andSelectors.add(new IdSelector<Resource>("@3"));
|
||||
andSelectors.add(new NameSelector<Resource>("Res 4"));
|
||||
|
@ -147,8 +144,9 @@ public class InMemoryQueryTest {
|
|||
InMemoryResourceDao dao = new InMemoryResourceDao();
|
||||
dao.saveAll(resources);
|
||||
|
||||
InMemoryQuery<Resource> ballQuery = new InMemoryQuery<>();
|
||||
InMemoryQuery<Resource, Resource> ballQuery = new InMemoryQuery<>();
|
||||
ballQuery.setNavigator(new AnyNavigator<Resource>());
|
||||
ballQuery.setElementVisitor(new NoStrategyVisitor<Resource>());
|
||||
AndSelector<Resource> andSelector = new AndSelector<>();
|
||||
andSelector.with(ParameterSelector.<Resource> stringSelector("parameters", "color", "red"));
|
||||
andSelector.with(ParameterSelector.<Resource> booleanSelector("parameters", "forChildren", true));
|
||||
|
|
|
@ -35,7 +35,6 @@ import li.strolch.model.query.OrderQuery;
|
|||
import li.strolch.model.query.ParameterSelection;
|
||||
import li.strolch.model.query.ResourceQuery;
|
||||
import li.strolch.model.query.Selection;
|
||||
import li.strolch.model.query.StrolchTypeNavigation;
|
||||
import li.strolch.persistence.api.StrolchTransaction;
|
||||
import li.strolch.runtime.StrolchConstants;
|
||||
|
||||
|
@ -64,14 +63,14 @@ public class QueryTest {
|
|||
tx.getResourceMap().add(tx, res1);
|
||||
}
|
||||
|
||||
ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType"));
|
||||
ResourceQuery<Resource> query = ResourceQuery.resourceQuery("MyType");
|
||||
List<Selection> elementAndSelections = new ArrayList<>();
|
||||
elementAndSelections.add(new IdSelection("@1"));
|
||||
elementAndSelections.add(ParameterSelection.integerSelection(BAG_ID, "nbOfBooks", 33));
|
||||
query.and().with(elementAndSelections);
|
||||
|
||||
InMemoryResourceQueryVisitor resourceQuery = new InMemoryResourceQueryVisitor();
|
||||
InMemoryQuery<Resource> inMemoryQuery = resourceQuery.visit(query);
|
||||
InMemoryQuery<Resource, Resource> inMemoryQuery = resourceQuery.visit(query);
|
||||
List<Resource> result;
|
||||
try (StrolchTransaction tx = container.getRealm(StrolchConstants.DEFAULT_REALM).openTx()) {
|
||||
result = inMemoryQuery.doQuery(tx.getPersistenceHandler().getResourceDao(tx));
|
||||
|
@ -94,14 +93,14 @@ public class QueryTest {
|
|||
tx.getOrderMap().add(tx, o1);
|
||||
}
|
||||
|
||||
OrderQuery query = new OrderQuery(new StrolchTypeNavigation("MyType"));
|
||||
OrderQuery<Order> query = OrderQuery.orderQuery("MyType");
|
||||
List<Selection> elementAndSelections = new ArrayList<>();
|
||||
elementAndSelections.add(new IdSelection("@1"));
|
||||
elementAndSelections.add(ParameterSelection.integerSelection(BAG_ID, "nbOfBooks", 33));
|
||||
query.and().with(elementAndSelections);
|
||||
|
||||
InMemoryOrderQueryVisitor orderQuery = new InMemoryOrderQueryVisitor();
|
||||
InMemoryQuery<Order> inMemoryQuery = orderQuery.visit(query);
|
||||
InMemoryQuery<Order, Order> inMemoryQuery = orderQuery.visit(query);
|
||||
List<Order> result;
|
||||
try (StrolchTransaction tx = container.getRealm(StrolchConstants.DEFAULT_REALM).openTx()) {
|
||||
result = inMemoryQuery.doQuery(tx.getPersistenceHandler().getOrderDao(tx));
|
||||
|
@ -122,7 +121,7 @@ public class QueryTest {
|
|||
tx.getResourceMap().add(tx, res1);
|
||||
}
|
||||
|
||||
ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType"));
|
||||
ResourceQuery<Resource> query = ResourceQuery.resourceQuery("MyType");
|
||||
query.and().with(ParameterSelection.stringSelection(BAG_ID, PARAM_STRING_ID, "olch").contains(true));
|
||||
List<Resource> result;
|
||||
try (StrolchTransaction tx = container.getRealm(StrolchConstants.DEFAULT_REALM).openTx()) {
|
||||
|
@ -144,7 +143,7 @@ public class QueryTest {
|
|||
tx.getResourceMap().add(tx, res1);
|
||||
}
|
||||
|
||||
ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType"));
|
||||
ResourceQuery<Resource> query = ResourceQuery.resourceQuery("MyType");
|
||||
query.and().with(ParameterSelection.stringSelection(BAG_ID, PARAM_STRING_ID, "str").contains(true));
|
||||
List<Resource> result;
|
||||
try (StrolchTransaction tx = container.getRealm(StrolchConstants.DEFAULT_REALM).openTx()) {
|
||||
|
@ -165,7 +164,7 @@ public class QueryTest {
|
|||
tx.getResourceMap().add(tx, res1);
|
||||
}
|
||||
|
||||
ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType"));
|
||||
ResourceQuery<Resource> query = ResourceQuery.resourceQuery("MyType");
|
||||
query.and().with(ParameterSelection.stringSelection(BAG_ID, PARAM_STRING_ID, "strolch").caseInsensitive(true));
|
||||
List<Resource> result;
|
||||
try (StrolchTransaction tx = container.getRealm(StrolchConstants.DEFAULT_REALM).openTx()) {
|
||||
|
@ -187,7 +186,7 @@ public class QueryTest {
|
|||
tx.getResourceMap().add(tx, res1);
|
||||
}
|
||||
|
||||
ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType"));
|
||||
ResourceQuery<Resource> query = ResourceQuery.resourceQuery("MyType");
|
||||
query.and().with(ParameterSelection.stringSelection(BAG_ID, PARAM_STRING_ID, "strolch"));
|
||||
List<Resource> result;
|
||||
try (StrolchTransaction tx = container.getRealm(StrolchConstants.DEFAULT_REALM).openTx()) {
|
||||
|
@ -211,7 +210,7 @@ public class QueryTest {
|
|||
}
|
||||
|
||||
{
|
||||
ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType"));
|
||||
ResourceQuery<Resource> query = ResourceQuery.resourceQuery("MyType");
|
||||
query.not(new IdSelection("@1"));
|
||||
List<Resource> result;
|
||||
try (StrolchTransaction tx = container.getRealm(StrolchConstants.DEFAULT_REALM).openTx()) {
|
||||
|
@ -222,7 +221,7 @@ public class QueryTest {
|
|||
}
|
||||
|
||||
{
|
||||
ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType"));
|
||||
ResourceQuery<Resource> query = ResourceQuery.resourceQuery("MyType");
|
||||
query.not(new IdSelection("@2"));
|
||||
List<Resource> result;
|
||||
try (StrolchTransaction tx = container.getRealm(StrolchConstants.DEFAULT_REALM).openTx()) {
|
||||
|
@ -233,7 +232,7 @@ public class QueryTest {
|
|||
}
|
||||
|
||||
{
|
||||
ResourceQuery query = new ResourceQuery(new StrolchTypeNavigation("MyType"));
|
||||
ResourceQuery<Resource> query = ResourceQuery.resourceQuery("MyType");
|
||||
query.not(new IdSelection("@1", "@2"));
|
||||
List<Resource> result;
|
||||
try (StrolchTransaction tx = container.getRealm(StrolchConstants.DEFAULT_REALM).openTx()) {
|
||||
|
|
Loading…
Reference in New Issue