> future = executorService.submit(() -> userDao.insertAndGetConflictEmails(chunk));
+ chunkFutures.put(emailRange, future);
+ log.info("Submit chunk: " + emailRange);
+ }
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java
new file mode 100644
index 000000000..6a83965e1
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/CityType.java
@@ -0,0 +1,89 @@
+
+
+package ru.javaops.masterjava.xml.schema;
+
+import javax.xml.bind.annotation.*;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+
+/**
+ * Java class for cityType complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType name="cityType">
+ * <simpleContent>
+ * <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ * <attribute name="id" use="required" type="{http://www.w3.org/2001/XMLSchema}ID" />
+ * </extension>
+ * </simpleContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "cityType", namespace = "http://javaops.ru", propOrder = {
+ "value"
+})
+public class CityType {
+
+ @XmlValue
+ protected String value;
+ @XmlAttribute(name = "id", required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlID
+ @XmlSchemaType(name = "ID")
+ protected String id;
+
+ /**
+ * Gets the value of the value property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of the value property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Gets the value of the id property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Sets the value of the id property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setId(String value) {
+ this.id = value;
+ }
+
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java
new file mode 100644
index 000000000..eda39fa9a
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/FlagType.java
@@ -0,0 +1,54 @@
+
+package ru.javaops.masterjava.xml.schema;
+
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlEnumValue;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * Java class for flagType.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <simpleType name="flagType">
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}string">
+ * <enumeration value="active"/>
+ * <enumeration value="deleted"/>
+ * <enumeration value="superuser"/>
+ * </restriction>
+ * </simpleType>
+ *
+ *
+ */
+@XmlType(name = "flagType", namespace = "http://javaops.ru")
+@XmlEnum
+public enum FlagType {
+
+ @XmlEnumValue("active")
+ ACTIVE("active"),
+ @XmlEnumValue("deleted")
+ DELETED("deleted"),
+ @XmlEnumValue("superuser")
+ SUPERUSER("superuser");
+ private final String value;
+
+ FlagType(String v) {
+ value = v;
+ }
+
+ public String value() {
+ return value;
+ }
+
+ public static FlagType fromValue(String v) {
+ for (FlagType c: FlagType.values()) {
+ if (c.value.equals(v)) {
+ return c;
+ }
+ }
+ throw new IllegalArgumentException(v);
+ }
+
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java
new file mode 100644
index 000000000..d5041640b
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/GroupType.java
@@ -0,0 +1,40 @@
+
+package ru.javaops.masterjava.xml.schema;
+
+import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * Java class for groupType.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <simpleType name="groupType">
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}string">
+ * <enumeration value="REGISTERING"/>
+ * <enumeration value="CURRENT"/>
+ * <enumeration value="FINISHED"/>
+ * </restriction>
+ * </simpleType>
+ *
+ *
+ */
+@XmlType(name = "groupType", namespace = "http://javaops.ru")
+@XmlEnum
+public enum GroupType {
+
+ REGISTERING,
+ CURRENT,
+ FINISHED;
+
+ public String value() {
+ return name();
+ }
+
+ public static GroupType fromValue(String v) {
+ return valueOf(v);
+ }
+
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java
new file mode 100644
index 000000000..bfb393299
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/ObjectFactory.java
@@ -0,0 +1,109 @@
+
+package ru.javaops.masterjava.xml.schema;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.annotation.XmlElementDecl;
+import javax.xml.bind.annotation.XmlRegistry;
+import javax.xml.namespace.QName;
+
+
+/**
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the ru.javaops.masterjava.xml.schema package.
+ * An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+@XmlRegistry
+public class ObjectFactory {
+
+ private final static QName _City_QNAME = new QName("http://javaops.ru", "City");
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: ru.javaops.masterjava.xml.schema
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link Project }
+ *
+ */
+ public Project createProject() {
+ return new Project();
+ }
+
+ /**
+ * Create an instance of {@link Payload }
+ *
+ */
+ public Payload createPayload() {
+ return new Payload();
+ }
+
+ /**
+ * Create an instance of {@link Project.Group }
+ *
+ */
+ public Project.Group createProjectGroup() {
+ return new Project.Group();
+ }
+
+ /**
+ * Create an instance of {@link User }
+ *
+ */
+ public User createUser() {
+ return new User();
+ }
+
+ /**
+ * Create an instance of {@link Payload.Projects }
+ *
+ */
+ public Payload.Projects createPayloadProjects() {
+ return new Payload.Projects();
+ }
+
+ /**
+ * Create an instance of {@link Payload.Cities }
+ *
+ */
+ public Payload.Cities createPayloadCities() {
+ return new Payload.Cities();
+ }
+
+ /**
+ * Create an instance of {@link Payload.Users }
+ *
+ */
+ public Payload.Users createPayloadUsers() {
+ return new Payload.Users();
+ }
+
+ /**
+ * Create an instance of {@link CityType }
+ *
+ */
+ public CityType createCityType() {
+ return new CityType();
+ }
+
+ /**
+ * Create an instance of {@link JAXBElement }{@code <}{@link CityType }{@code >}}
+ *
+ */
+ @XmlElementDecl(namespace = "http://javaops.ru", name = "City")
+ public JAXBElement createCity(CityType value) {
+ return new JAXBElement(_City_QNAME, CityType.class, null, value);
+ }
+
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java
new file mode 100644
index 000000000..9d4cc3046
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/Payload.java
@@ -0,0 +1,328 @@
+
+package ru.javaops.masterjava.xml.schema;
+
+import javax.xml.bind.annotation.*;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="Projects">
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence maxOccurs="unbounded">
+ * <element ref="{http://javaops.ru}Project"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </element>
+ * <element name="Cities">
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence maxOccurs="unbounded">
+ * <element ref="{http://javaops.ru}City"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </element>
+ * <element name="Users">
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence maxOccurs="unbounded" minOccurs="0">
+ * <element ref="{http://javaops.ru}User"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </element>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "projects",
+ "cities",
+ "users"
+})
+@XmlRootElement(name = "Payload", namespace = "http://javaops.ru")
+public class Payload {
+
+ @XmlElement(name = "Projects", namespace = "http://javaops.ru", required = true)
+ protected Payload.Projects projects;
+ @XmlElement(name = "Cities", namespace = "http://javaops.ru", required = true)
+ protected Payload.Cities cities;
+ @XmlElement(name = "Users", namespace = "http://javaops.ru", required = true)
+ protected Payload.Users users;
+
+ /**
+ * Gets the value of the projects property.
+ *
+ * @return
+ * possible object is
+ * {@link Payload.Projects }
+ *
+ */
+ public Payload.Projects getProjects() {
+ return projects;
+ }
+
+ /**
+ * Sets the value of the projects property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Payload.Projects }
+ *
+ */
+ public void setProjects(Payload.Projects value) {
+ this.projects = value;
+ }
+
+ /**
+ * Gets the value of the cities property.
+ *
+ * @return
+ * possible object is
+ * {@link Payload.Cities }
+ *
+ */
+ public Payload.Cities getCities() {
+ return cities;
+ }
+
+ /**
+ * Sets the value of the cities property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Payload.Cities }
+ *
+ */
+ public void setCities(Payload.Cities value) {
+ this.cities = value;
+ }
+
+ /**
+ * Gets the value of the users property.
+ *
+ * @return
+ * possible object is
+ * {@link Payload.Users }
+ *
+ */
+ public Payload.Users getUsers() {
+ return users;
+ }
+
+ /**
+ * Sets the value of the users property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Payload.Users }
+ *
+ */
+ public void setUsers(Payload.Users value) {
+ this.users = value;
+ }
+
+
+ /**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence maxOccurs="unbounded">
+ * <element ref="{http://javaops.ru}City"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "", propOrder = {
+ "city"
+ })
+ public static class Cities {
+
+ @XmlElement(name = "City", namespace = "http://javaops.ru", required = true)
+ protected List city;
+
+ /**
+ * Gets the value of the city property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a set method for the city property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getCity().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link CityType }
+ *
+ *
+ */
+ public List getCity() {
+ if (city == null) {
+ city = new ArrayList();
+ }
+ return this.city;
+ }
+
+ }
+
+
+ /**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence maxOccurs="unbounded">
+ * <element ref="{http://javaops.ru}Project"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "", propOrder = {
+ "project"
+ })
+ public static class Projects {
+
+ @XmlElement(name = "Project", namespace = "http://javaops.ru", required = true)
+ protected List project;
+
+ /**
+ * Gets the value of the project property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a set method for the project property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getProject().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link Project }
+ *
+ *
+ */
+ public List getProject() {
+ if (project == null) {
+ project = new ArrayList();
+ }
+ return this.project;
+ }
+
+ }
+
+
+ /**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence maxOccurs="unbounded" minOccurs="0">
+ * <element ref="{http://javaops.ru}User"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "", propOrder = {
+ "user"
+ })
+ public static class Users {
+
+ @XmlElement(name = "User", namespace = "http://javaops.ru")
+ protected List user;
+
+ /**
+ * Gets the value of the user property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a set method for the user property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getUser().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link User }
+ *
+ *
+ */
+ public List getUser() {
+ if (user == null) {
+ user = new ArrayList();
+ }
+ return this.user;
+ }
+
+ }
+
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/Project.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/Project.java
new file mode 100644
index 000000000..180a997e9
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/Project.java
@@ -0,0 +1,216 @@
+
+package ru.javaops.masterjava.xml.schema;
+
+import javax.xml.bind.annotation.*;
+import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element name="description" type="{http://www.w3.org/2001/XMLSchema}string"/>
+ * <sequence maxOccurs="unbounded">
+ * <element name="Group">
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}ID" />
+ * <attribute name="type" use="required" type="{http://javaops.ru}groupType" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ * </element>
+ * </sequence>
+ * </sequence>
+ * <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "description",
+ "group"
+})
+@XmlRootElement(name = "Project", namespace = "http://javaops.ru")
+public class Project {
+
+ @XmlElement(namespace = "http://javaops.ru", required = true)
+ protected String description;
+ @XmlElement(name = "Group", namespace = "http://javaops.ru", required = true)
+ protected List group;
+ @XmlAttribute(name = "name", required = true)
+ protected String name;
+
+ /**
+ * Gets the value of the description property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Sets the value of the description property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setDescription(String value) {
+ this.description = value;
+ }
+
+ /**
+ * Gets the value of the group property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a set method for the group property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getGroup().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link Project.Group }
+ *
+ *
+ */
+ public List getGroup() {
+ if (group == null) {
+ group = new ArrayList();
+ }
+ return this.group;
+ }
+
+ /**
+ * Gets the value of the name property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the name property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setName(String value) {
+ this.name = value;
+ }
+
+
+ /**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}ID" />
+ * <attribute name="type" use="required" type="{http://javaops.ru}groupType" />
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(name = "")
+ public static class Group {
+
+ @XmlAttribute(name = "name", required = true)
+ @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
+ @XmlID
+ @XmlSchemaType(name = "ID")
+ protected String name;
+ @XmlAttribute(name = "type", required = true)
+ protected GroupType type;
+
+ /**
+ * Gets the value of the name property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the name property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setName(String value) {
+ this.name = value;
+ }
+
+ /**
+ * Gets the value of the type property.
+ *
+ * @return
+ * possible object is
+ * {@link GroupType }
+ *
+ */
+ public GroupType getType() {
+ return type;
+ }
+
+ /**
+ * Sets the value of the type property.
+ *
+ * @param value
+ * allowed object is
+ * {@link GroupType }
+ *
+ */
+ public void setType(GroupType value) {
+ this.type = value;
+ }
+
+ }
+
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/User.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/User.java
new file mode 100644
index 000000000..b2cc1c449
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/schema/User.java
@@ -0,0 +1,180 @@
+
+package ru.javaops.masterjava.xml.schema;
+
+import javax.xml.bind.annotation.*;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <simpleContent>
+ * <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ * <attribute name="email" type="{http://javaops.ru}emailAddressType" />
+ * <attribute name="flag" use="required" type="{http://javaops.ru}flagType" />
+ * <attribute name="city" use="required" type="{http://www.w3.org/2001/XMLSchema}IDREF" />
+ * <attribute name="groupRefs" type="{http://www.w3.org/2001/XMLSchema}IDREFS" />
+ * </extension>
+ * </simpleContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "value"
+})
+@XmlRootElement(name = "User", namespace = "http://javaops.ru")
+public class User {
+
+ @XmlValue
+ protected String value;
+ @XmlAttribute(name = "email")
+ protected String email;
+ @XmlAttribute(name = "flag", required = true)
+ protected FlagType flag;
+ @XmlAttribute(name = "city", required = true)
+ @XmlIDREF
+ @XmlSchemaType(name = "IDREF")
+ protected Object city;
+ @XmlAttribute(name = "groupRefs")
+ @XmlIDREF
+ @XmlSchemaType(name = "IDREFS")
+ protected List groupRefs;
+
+ /**
+ * Gets the value of the value property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of the value property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Gets the value of the email property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getEmail() {
+ return email;
+ }
+
+ /**
+ * Sets the value of the email property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setEmail(String value) {
+ this.email = value;
+ }
+
+ /**
+ * Gets the value of the flag property.
+ *
+ * @return
+ * possible object is
+ * {@link FlagType }
+ *
+ */
+ public FlagType getFlag() {
+ return flag;
+ }
+
+ /**
+ * Sets the value of the flag property.
+ *
+ * @param value
+ * allowed object is
+ * {@link FlagType }
+ *
+ */
+ public void setFlag(FlagType value) {
+ this.flag = value;
+ }
+
+ /**
+ * Gets the value of the city property.
+ *
+ * @return
+ * possible object is
+ * {@link Object }
+ *
+ */
+ public Object getCity() {
+ return city;
+ }
+
+ /**
+ * Sets the value of the city property.
+ *
+ * @param value
+ * allowed object is
+ * {@link Object }
+ *
+ */
+ public void setCity(Object value) {
+ this.city = value;
+ }
+
+ /**
+ * Gets the value of the groupRefs property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a set method for the groupRefs property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getGroupRefs().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link Object }
+ *
+ *
+ */
+ public List getGroupRefs() {
+ if (groupRefs == null) {
+ groupRefs = new ArrayList();
+ }
+ return this.groupRefs;
+ }
+
+ @Override
+ public String toString() {
+ return value + '(' + email + ')';
+ }
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java
new file mode 100644
index 000000000..507825dda
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbMarshaller.java
@@ -0,0 +1,42 @@
+package ru.javaops.masterjava.xml.util;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.PropertyException;
+import javax.xml.validation.Schema;
+import java.io.StringWriter;
+import java.io.Writer;
+
+public class JaxbMarshaller {
+ private Marshaller marshaller;
+
+ public JaxbMarshaller(JAXBContext ctx) throws JAXBException {
+ marshaller = ctx.createMarshaller();
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
+ marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
+ }
+
+ public void setProperty(String prop, Object value) {
+ try {
+ marshaller.setProperty(prop, value);
+ } catch (PropertyException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public void setSchema(Schema schema) {
+ marshaller.setSchema(schema);
+ }
+
+ public String marshal(Object instance) throws JAXBException {
+ StringWriter sw = new StringWriter();
+ marshal(instance, sw);
+ return sw.toString();
+ }
+
+ public void marshal(Object instance, Writer writer) throws JAXBException {
+ marshaller.marshal(instance, writer);
+ }
+}
\ No newline at end of file
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java
new file mode 100644
index 000000000..8aff55510
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbParser.java
@@ -0,0 +1,80 @@
+package ru.javaops.masterjava.xml.util;
+
+import org.xml.sax.SAXException;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+
+/**
+ * Marshalling/Unmarshalling JAXB facade
+ */
+public class JaxbParser {
+
+ private JAXBContext ctx;
+ protected Schema schema;
+
+ public JaxbParser(Class... classesToBeBound) {
+ try {
+ init(JAXBContext.newInstance(classesToBeBound));
+ } catch (JAXBException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ // http://stackoverflow.com/questions/30643802/what-is-jaxbcontext-newinstancestring-contextpath
+ public JaxbParser(String context) {
+ try {
+ init(JAXBContext.newInstance(context));
+ } catch (JAXBException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void init(JAXBContext ctx) {
+ this.ctx = ctx;
+ }
+
+ // https://stackoverflow.com/a/7400735/548473
+ public JaxbMarshaller createMarshaller() {
+ try {
+ JaxbMarshaller marshaller = new JaxbMarshaller(ctx);
+ if (schema != null) {
+ marshaller.setSchema(schema);
+ }
+ return marshaller;
+ } catch (JAXBException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ // https://stackoverflow.com/a/7400735/548473
+ public JaxbUnmarshaller createUnmarshaller() {
+ try {
+ JaxbUnmarshaller unmarshaller = new JaxbUnmarshaller(ctx);
+ if (schema != null) {
+ unmarshaller.setSchema(schema);
+ }
+ return unmarshaller;
+ } catch (JAXBException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public void setSchema(Schema schema) {
+ this.schema = schema;
+ }
+
+ public void validate(String str) throws IOException, SAXException {
+ validate(new StringReader(str));
+ }
+
+ public void validate(Reader reader) throws IOException, SAXException {
+ schema.newValidator().validate(new StreamSource(reader));
+ }
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java
new file mode 100644
index 000000000..f9f29c2c9
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/JaxbUnmarshaller.java
@@ -0,0 +1,38 @@
+package ru.javaops.masterjava.xml.util;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.validation.Schema;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+
+public class JaxbUnmarshaller {
+ private Unmarshaller unmarshaller;
+
+ public JaxbUnmarshaller(JAXBContext ctx) throws JAXBException {
+ unmarshaller = ctx.createUnmarshaller();
+ }
+
+ public void setSchema(Schema schema) {
+ unmarshaller.setSchema(schema);
+ }
+
+ public T unmarshal(InputStream is) throws JAXBException {
+ return (T) unmarshaller.unmarshal(is);
+ }
+
+ public T unmarshal(Reader reader) throws JAXBException {
+ return (T) unmarshaller.unmarshal(reader);
+ }
+
+ public T unmarshal(String str) throws JAXBException {
+ return (T) unmarshal(new StringReader(str));
+ }
+
+ public T unmarshal(XMLStreamReader reader, Class elementClass) throws JAXBException {
+ return unmarshaller.unmarshal(reader, elementClass).getValue();
+ }
+}
\ No newline at end of file
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java
new file mode 100644
index 000000000..42f41df80
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/Schemas.java
@@ -0,0 +1,48 @@
+package ru.javaops.masterjava.xml.util;
+
+import com.google.common.io.Resources;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import java.io.File;
+import java.io.StringReader;
+import java.net.URL;
+
+
+public class Schemas {
+
+ // SchemaFactory is not thread-safe
+ private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+
+ public static synchronized Schema ofString(String xsd) {
+ try {
+ return SCHEMA_FACTORY.newSchema(new StreamSource(new StringReader(xsd)));
+ } catch (SAXException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static synchronized Schema ofClasspath(String resource) {
+ // http://digitalsanctum.com/2012/11/30/how-to-read-file-contents-in-java-the-easy-way-with-guava/
+ return ofURL(Resources.getResource(resource));
+ }
+
+ public static synchronized Schema ofURL(URL url) {
+ try {
+ return SCHEMA_FACTORY.newSchema(url);
+ } catch (SAXException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static synchronized Schema ofFile(File file) {
+ try {
+ return SCHEMA_FACTORY.newSchema(file);
+ } catch (SAXException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java
new file mode 100644
index 000000000..5878118c0
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/StaxStreamProcessor.java
@@ -0,0 +1,79 @@
+package ru.javaops.masterjava.xml.util;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.events.XMLEvent;
+import java.io.InputStream;
+
+public class StaxStreamProcessor implements AutoCloseable {
+ private static final XMLInputFactory FACTORY = XMLInputFactory.newInstance();
+
+ private final XMLStreamReader reader;
+
+ public StaxStreamProcessor(InputStream is) throws XMLStreamException {
+ reader = FACTORY.createXMLStreamReader(is);
+ }
+
+ public boolean startElement(String element, String parent) throws XMLStreamException {
+ while (reader.hasNext()) {
+ int event = reader.next();
+ if (parent != null && isElementEnd(event, parent)) {
+ return false;
+ }
+ if (isElementStart(event, element)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isElementStart(int event, String el) {
+ return event == XMLEvent.START_ELEMENT && el.equals(reader.getLocalName());
+ }
+
+ private boolean isElementEnd(int event, String el) {
+ return event == XMLEvent.END_ELEMENT && el.equals(reader.getLocalName());
+ }
+
+ public XMLStreamReader getReader() {
+ return reader;
+ }
+
+ public String getAttribute(String name) throws XMLStreamException {
+ return reader.getAttributeValue(null, name);
+ }
+
+ public boolean doUntil(int stopEvent, String value) throws XMLStreamException {
+ while (reader.hasNext()) {
+ int event = reader.next();
+ if (event == stopEvent && value.equals(getValue(event))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String getValue(int event) throws XMLStreamException {
+ return (event == XMLEvent.CHARACTERS) ? reader.getText() : reader.getLocalName();
+ }
+
+ public String getElementValue(String element) throws XMLStreamException {
+ return doUntil(XMLEvent.START_ELEMENT, element) ? reader.getElementText() : null;
+ }
+
+ public String getText() throws XMLStreamException {
+ return reader.getElementText();
+ }
+
+ @Override
+ public void close() {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (XMLStreamException e) {
+ // empty
+ }
+ }
+ }
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java
new file mode 100644
index 000000000..63baae5d1
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/XPathProcessor.java
@@ -0,0 +1,58 @@
+package ru.javaops.masterjava.xml.util;
+
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class XPathProcessor {
+ private static final DocumentBuilderFactory DOCUMENT_FACTORY = DocumentBuilderFactory.newInstance();
+ private static final DocumentBuilder DOCUMENT_BUILDER;
+
+ private static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance();
+ private static final XPath XPATH = XPATH_FACTORY.newXPath();
+
+ static {
+ DOCUMENT_FACTORY.setNamespaceAware(true);
+ try {
+ DOCUMENT_BUILDER = DOCUMENT_FACTORY.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private final Document doc;
+
+ public XPathProcessor(InputStream is) {
+ try {
+ doc = DOCUMENT_BUILDER.parse(is);
+ } catch (SAXException | IOException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public static synchronized XPathExpression getExpression(String exp) {
+ try {
+ return XPATH.compile(exp);
+ } catch (XPathExpressionException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ public T evaluate(XPathExpression expression, QName type) {
+ try {
+ return (T) expression.evaluate(doc, type);
+ } catch (XPathExpressionException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+}
diff --git a/web/upload/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java
new file mode 100644
index 000000000..083febb00
--- /dev/null
+++ b/web/upload/src/main/java/ru/javaops/masterjava/xml/util/XsltProcessor.java
@@ -0,0 +1,47 @@
+package ru.javaops.masterjava.xml.util;
+
+import javax.xml.transform.*;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+public class XsltProcessor {
+ private static TransformerFactory FACTORY = TransformerFactory.newInstance();
+ private final Transformer xformer;
+
+ public XsltProcessor(InputStream xslInputStream) {
+ this(new BufferedReader(new InputStreamReader(xslInputStream, StandardCharsets.UTF_8)));
+ }
+
+ public XsltProcessor(Reader xslReader) {
+ try {
+ Templates template = FACTORY.newTemplates(new StreamSource(xslReader));
+ xformer = template.newTransformer();
+ } catch (TransformerConfigurationException e) {
+ throw new IllegalStateException("XSLT transformer creation failed: " + e.toString(), e);
+ }
+ }
+
+ public String transform(InputStream xmlInputStream) throws TransformerException {
+ StringWriter out = new StringWriter();
+ transform(xmlInputStream, out);
+ return out.getBuffer().toString();
+ }
+
+ public void transform(InputStream xmlInputStream, Writer result) throws TransformerException {
+ transform(new BufferedReader(new InputStreamReader(xmlInputStream, StandardCharsets.UTF_8)), result);
+ }
+
+ public void transform(Reader sourceReader, Writer result) throws TransformerException {
+ xformer.transform(new StreamSource(sourceReader), new StreamResult(result));
+ }
+
+ public static String getXsltHeader(String xslt) {
+ return "\n";
+ }
+
+ public void setParameter(String name, String value) {
+ xformer.setParameter(name, value);
+ }
+}
diff --git a/web/upload/src/main/resources/cities.xsl b/web/upload/src/main/resources/cities.xsl
new file mode 100644
index 000000000..1c509124b
--- /dev/null
+++ b/web/upload/src/main/resources/cities.xsl
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/upload/src/main/resources/groups.xsl b/web/upload/src/main/resources/groups.xsl
new file mode 100644
index 000000000..c5ade36b8
--- /dev/null
+++ b/web/upload/src/main/resources/groups.xsl
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+ groups
+
+
+
+
+ groups
+
+
+
+ Group
+ Type
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/upload/src/main/resources/payload.xsd b/web/upload/src/main/resources/payload.xsd
new file mode 100644
index 000000000..8288f5a7d
--- /dev/null
+++ b/web/upload/src/main/resources/payload.xsd
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/upload/src/main/webapp/WEB-INF/templates/exception.html b/web/upload/src/main/webapp/WEB-INF/templates/exception.html
new file mode 100644
index 000000000..2b5e6fe5a
--- /dev/null
+++ b/web/upload/src/main/webapp/WEB-INF/templates/exception.html
@@ -0,0 +1,20 @@
+
+
+
+ Application error
+
+
+
+
+
+
+
Application error:
+
+
exception.message
+
+
+
+
+
\ No newline at end of file
diff --git a/web/upload/src/main/webapp/WEB-INF/templates/result.html b/web/upload/src/main/webapp/WEB-INF/templates/result.html
new file mode 100644
index 000000000..497dffe3f
--- /dev/null
+++ b/web/upload/src/main/webapp/WEB-INF/templates/result.html
@@ -0,0 +1,14 @@
+
+
+
+ Failed users
+
+
+
+Failed users
+
+
+
\ No newline at end of file
diff --git a/web/upload/src/main/webapp/WEB-INF/templates/upload.html b/web/upload/src/main/webapp/WEB-INF/templates/upload.html
new file mode 100644
index 000000000..12351c91c
--- /dev/null
+++ b/web/upload/src/main/webapp/WEB-INF/templates/upload.html
@@ -0,0 +1,22 @@
+
+
+
+ Upload XML
+
+
+
+
+
\ No newline at end of file
diff --git a/web/upload/src/test/java/ru/javaops/masterjava/MainXml.java b/web/upload/src/test/java/ru/javaops/masterjava/MainXml.java
new file mode 100644
index 000000000..ce2e3a902
--- /dev/null
+++ b/web/upload/src/test/java/ru/javaops/masterjava/MainXml.java
@@ -0,0 +1,136 @@
+package ru.javaops.masterjava;
+
+import com.google.common.base.Splitter;
+import com.google.common.io.Resources;
+import j2html.tags.ContainerTag;
+import one.util.streamex.StreamEx;
+import ru.javaops.masterjava.xml.schema.ObjectFactory;
+import ru.javaops.masterjava.xml.schema.Payload;
+import ru.javaops.masterjava.xml.schema.Project;
+import ru.javaops.masterjava.xml.schema.User;
+import ru.javaops.masterjava.xml.util.*;
+
+import javax.xml.stream.events.XMLEvent;
+import java.io.InputStream;
+import java.io.Writer;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.*;
+
+import static com.google.common.base.Strings.nullToEmpty;
+import static j2html.TagCreator.*;
+
+public class MainXml {
+
+ private static final Comparator USER_COMPARATOR = Comparator.comparing(User::getValue).thenComparing(User::getEmail);
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 1) {
+ System.out.println("Required argument: projectName");
+ System.exit(1);
+ }
+ String projectName = args[0];
+ URL payloadUrl = Resources.getResource("payload.xml");
+
+ Set users = parseByJaxb(projectName, payloadUrl);
+ users.forEach(System.out::println);
+
+ System.out.println();
+ String html = toHtml(users, projectName);
+ System.out.println(html);
+ try (Writer writer = Files.newBufferedWriter(Paths.get("out/users.html"))) {
+ writer.write(html);
+ }
+
+ System.out.println();
+ users = processByStax(projectName, payloadUrl);
+ users.forEach(System.out::println);
+
+ System.out.println();
+ html = transform(projectName, payloadUrl);
+ try (Writer writer = Files.newBufferedWriter(Paths.get("out/groups.html"))) {
+ writer.write(html);
+ }
+ }
+
+ private static Set parseByJaxb(String projectName, URL payloadUrl) throws Exception {
+ JaxbParser parser = new JaxbParser(ObjectFactory.class);
+ JaxbUnmarshaller unmarshaller = parser.createUnmarshaller();
+ parser.setSchema(Schemas.ofClasspath("payload.xsd"));
+ Payload payload;
+ try (InputStream is = payloadUrl.openStream()) {
+ payload = unmarshaller.unmarshal(is);
+ }
+
+ Project project = StreamEx.of(payload.getProjects().getProject())
+ .filter(p -> p.getName().equals(projectName))
+ .findAny()
+ .orElseThrow(() -> new IllegalArgumentException("Invalid project name '" + projectName + '\''));
+
+ final Set groups = new HashSet<>(project.getGroup()); // identity compare
+ return StreamEx.of(payload.getUsers().getUser())
+ .filter(u -> !Collections.disjoint(groups, u.getGroupRefs()))
+ .toCollection(() -> new TreeSet<>(USER_COMPARATOR));
+ }
+
+ private static Set processByStax(String projectName, URL payloadUrl) throws Exception {
+
+ try (InputStream is = payloadUrl.openStream()) {
+ StaxStreamProcessor processor = new StaxStreamProcessor(is);
+ final Set groupNames = new HashSet<>();
+
+ // Projects loop
+ projects:
+ while (processor.startElement("Project", "Projects")) {
+ if (projectName.equals(processor.getAttribute("name"))) {
+ while (processor.startElement("Group", "Project")) {
+ groupNames.add(processor.getAttribute("name"));
+ }
+ break;
+ }
+ }
+ if (groupNames.isEmpty()) {
+ throw new IllegalArgumentException("Invalid " + projectName + " or no groups");
+ }
+
+ // Users loop
+ Set users = new TreeSet<>(USER_COMPARATOR);
+
+ JaxbParser parser = new JaxbParser(ObjectFactory.class);
+ JaxbUnmarshaller unmarshaller = parser.createUnmarshaller();
+ while (processor.doUntil(XMLEvent.START_ELEMENT, "User")) {
+ String groupRefs = processor.getAttribute("groupRefs");
+ if (!Collections.disjoint(groupNames, Splitter.on(' ').splitToList(nullToEmpty(groupRefs)))) {
+ User user = unmarshaller.unmarshal(processor.getReader(), User.class);
+ users.add(user);
+ }
+ }
+ return users;
+ }
+ }
+
+ private static String toHtml(Set users, String projectName) {
+ final ContainerTag table = table().with(
+ tr().with(th("FullName"), th("email")))
+ .attr("border", "1")
+ .attr("cellpadding", "8")
+ .attr("cellspacing", "0");
+
+ users.forEach(u -> table.with(tr().with(td(u.getValue()), td(u.getEmail()))));
+
+ return html().with(
+ head().with(title(projectName + " users")),
+ body().with(h1(projectName + " users"), table)
+ ).render();
+ }
+
+ private static String transform(String projectName, URL payloadUrl) throws Exception {
+ URL xsl = Resources.getResource("groups.xsl");
+ try (InputStream xmlStream = payloadUrl.openStream(); InputStream xslStream = xsl.openStream()) {
+ XsltProcessor processor = new XsltProcessor(xslStream);
+ processor.setParameter("projectName", projectName);
+ return processor.transform(xmlStream);
+ }
+ }
+}
diff --git a/web/upload/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java
new file mode 100644
index 000000000..4754b17d6
--- /dev/null
+++ b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/JaxbParserTest.java
@@ -0,0 +1,46 @@
+package ru.javaops.masterjava.xml.util;
+
+import com.google.common.io.Resources;
+import org.junit.Test;
+import ru.javaops.masterjava.xml.schema.CityType;
+import ru.javaops.masterjava.xml.schema.ObjectFactory;
+import ru.javaops.masterjava.xml.schema.Payload;
+
+import javax.xml.bind.JAXBElement;
+import javax.xml.namespace.QName;
+
+public class JaxbParserTest {
+ // https://google.github.io/styleguide/javaguide.html#s5.2.4-constant-names
+ private static final JaxbParser jaxbParser;
+ private static final JaxbMarshaller marshaller;
+ private static final JaxbUnmarshaller unmarshaller;
+
+ static {
+ jaxbParser = new JaxbParser(ObjectFactory.class);
+ jaxbParser.setSchema(Schemas.ofClasspath("payload.xsd"));
+ marshaller = jaxbParser.createMarshaller();
+ unmarshaller = jaxbParser.createUnmarshaller();
+ }
+
+ @Test
+ public void testPayload() throws Exception {
+// JaxbParserTest.class.getResourceAsStream("/city.xml")
+ Payload payload = unmarshaller.unmarshal(
+ Resources.getResource("payload.xml").openStream());
+ String strPayload = marshaller.marshal(payload);
+ jaxbParser.validate(strPayload);
+ System.out.println(strPayload);
+ }
+
+ @Test
+ public void testCity() throws Exception {
+ JAXBElement cityElement = unmarshaller.unmarshal(
+ Resources.getResource("city.xml").openStream());
+ CityType city = cityElement.getValue();
+ JAXBElement cityElement2 =
+ new JAXBElement<>(new QName("http://javaops.ru", "City"), CityType.class, city);
+ String strCity = marshaller.marshal(cityElement2);
+ jaxbParser.validate(strCity);
+ System.out.println(strCity);
+ }
+}
\ No newline at end of file
diff --git a/web/upload/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java
new file mode 100644
index 000000000..fd55963dd
--- /dev/null
+++ b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/StaxStreamProcessorTest.java
@@ -0,0 +1,36 @@
+package ru.javaops.masterjava.xml.util;
+
+import com.google.common.io.Resources;
+import org.junit.Test;
+
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.events.XMLEvent;
+
+public class StaxStreamProcessorTest {
+ @Test
+ public void readCities() throws Exception {
+ try (StaxStreamProcessor processor =
+ new StaxStreamProcessor(Resources.getResource("payload.xml").openStream())) {
+ XMLStreamReader reader = processor.getReader();
+ while (reader.hasNext()) {
+ int event = reader.next();
+ if (event == XMLEvent.START_ELEMENT) {
+ if ("City".equals(reader.getLocalName())) {
+ System.out.println(reader.getElementText());
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ public void readCities2() throws Exception {
+ try (StaxStreamProcessor processor =
+ new StaxStreamProcessor(Resources.getResource("payload.xml").openStream())) {
+ String city;
+ while ((city = processor.getElementValue("City")) != null) {
+ System.out.println(city);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/upload/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java
new file mode 100644
index 000000000..199f676a1
--- /dev/null
+++ b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/XPathProcessorTest.java
@@ -0,0 +1,26 @@
+package ru.javaops.masterjava.xml.util;
+
+import com.google.common.io.Resources;
+import org.junit.Test;
+import org.w3c.dom.NodeList;
+
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import java.io.InputStream;
+import java.util.stream.IntStream;
+
+public class XPathProcessorTest {
+ @Test
+ public void getCities() throws Exception {
+ try (InputStream is =
+ Resources.getResource("payload.xml").openStream()) {
+ XPathProcessor processor = new XPathProcessor(is);
+ XPathExpression expression =
+ XPathProcessor.getExpression("/*[name()='Payload']/*[name()='Cities']/*[name()='City']/text()");
+ NodeList nodes = processor.evaluate(expression, XPathConstants.NODESET);
+ IntStream.range(0, nodes.getLength()).forEach(
+ i -> System.out.println(nodes.item(i).getNodeValue())
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/web/upload/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java
new file mode 100644
index 000000000..d7f42a699
--- /dev/null
+++ b/web/upload/src/test/java/ru/javaops/masterjava/xml/util/XsltProcessorTest.java
@@ -0,0 +1,18 @@
+package ru.javaops.masterjava.xml.util;
+
+import com.google.common.io.Resources;
+import org.junit.Test;
+
+import java.io.InputStream;
+
+public class XsltProcessorTest {
+ @Test
+ public void transform() throws Exception {
+ try (InputStream xslInputStream = Resources.getResource("cities.xsl").openStream();
+ InputStream xmlInputStream = Resources.getResource("payload.xml").openStream()) {
+
+ XsltProcessor processor = new XsltProcessor(xslInputStream);
+ System.out.println(processor.transform(xmlInputStream));
+ }
+ }
+}
diff --git a/web/upload/src/test/resources/city.xml b/web/upload/src/test/resources/city.xml
new file mode 100644
index 000000000..8b0abcf8a
--- /dev/null
+++ b/web/upload/src/test/resources/city.xml
@@ -0,0 +1,4 @@
+Санкт-Петербург
+
\ No newline at end of file
diff --git a/web/upload/src/test/resources/payload.xml b/web/upload/src/test/resources/payload.xml
new file mode 100644
index 000000000..fadf8c64a
--- /dev/null
+++ b/web/upload/src/test/resources/payload.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ Topjava
+
+
+
+
+
+ Masterjava
+
+
+
+
+ Санкт-Петербург
+ Москва
+ Киев
+ Минск
+
+
+ Full Name
+ Admin
+ Deleted
+ User1
+ User2
+ User3
+
+
\ No newline at end of file
diff --git a/web/upload/src/test/resources/payload_bad.xml b/web/upload/src/test/resources/payload_bad.xml
new file mode 100644
index 000000000..72a2e0422
--- /dev/null
+++ b/web/upload/src/test/resources/payload_bad.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ Topjava
+
+
+
+
+
+ Masterjava
+
+
+
+
+ Санкт-Петербург
+ Москва
+ Киев
+ Минск
+
+
+ Full Name
+ Admin
+ Deleted
+ User1
+ User2
+ User3
+
+
\ No newline at end of file
diff --git a/web/webapp/pom.xml b/web/webapp/pom.xml
new file mode 100644
index 000000000..de3a56fc2
--- /dev/null
+++ b/web/webapp/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+
+ ru.javaops
+ parent-web
+ ../../parent-web/pom.xml
+ 1.0-SNAPSHOT
+
+
+ webapp
+ 1.0-SNAPSHOT
+ war
+ WebApp
+
+
+ webapp
+
+
+
+
+ ${project.groupId}
+ persist
+ ${project.version}
+
+
+
\ No newline at end of file
diff --git a/web/webapp/src/main/java/ru/javaops/masterjava/webapp/UsersServlet.java b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/UsersServlet.java
new file mode 100644
index 000000000..5b587128f
--- /dev/null
+++ b/web/webapp/src/main/java/ru/javaops/masterjava/webapp/UsersServlet.java
@@ -0,0 +1,27 @@
+package ru.javaops.masterjava.webapp;
+
+import com.google.common.collect.ImmutableMap;
+import org.thymeleaf.context.WebContext;
+import ru.javaops.masterjava.persist.DBIProvider;
+import ru.javaops.masterjava.persist.dao.UserDao;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static ru.javaops.masterjava.common.web.ThymeleafListener.engine;
+
+@WebServlet("")
+public class UsersServlet extends HttpServlet {
+ private UserDao userDao = DBIProvider.getDao(UserDao.class);
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+ final WebContext webContext = new WebContext(req, resp, req.getServletContext(), req.getLocale(),
+ ImmutableMap.of("users", userDao.getWithLimit(20)));
+ engine.process("users", webContext, resp.getWriter());
+ }
+}
diff --git a/web/webapp/src/main/webapp/WEB-INF/templates/users.html b/web/webapp/src/main/webapp/WEB-INF/templates/users.html
new file mode 100644
index 000000000..0ae713167
--- /dev/null
+++ b/web/webapp/src/main/webapp/WEB-INF/templates/users.html
@@ -0,0 +1,27 @@
+
+
+
+ Users
+
+
+
+
+
+ #
+ Full Name
+ Email
+ Flag
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file