Compare commits

..

No commits in common. "2ae9509782aa71e7a05fe775280d7de4eafdec58" and "4cc569678883ce49872227d34d8a80b42ec0f329" have entirely different histories.

98 changed files with 268 additions and 7208 deletions

View File

@ -1,198 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# List of comma-separated paths defining the contents of the "common"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank,the JVM system loader will be used as Catalina's "common"
# loader.
# Examples:
# "foo": Add this folder as a class repository
# "foo/*.jar": Add all the JARs of the specified folder as class
# repositories
# "foo/bar.jar": Add bar.jar as a class repository
#
# Note: Values are enclosed in double quotes ("...") in case either the
# ${catalina.base} path or the ${catalina.home} path contains a comma.
# Because double quotes are used for quoting, the double quote character
# may not appear in a path.
common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
#
# List of comma-separated paths defining the contents of the "server"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
# If left as blank, the "common" loader will be used as Catalina's "server"
# loader.
# Examples:
# "foo": Add this folder as a class repository
# "foo/*.jar": Add all the JARs of the specified folder as class
# repositories
# "foo/bar.jar": Add bar.jar as a class repository
#
# Note: Values may be enclosed in double quotes ("...") in case either the
# ${catalina.base} path or the ${catalina.home} path contains a comma.
# Because double quotes are used for quoting, the double quote character
# may not appear in a path.
server.loader=
#
# List of comma-separated paths defining the contents of the "shared"
# classloader. Prefixes should be used to define what is the repository type.
# Path may be relative to the CATALINA_BASE path or absolute. If left as blank,
# the "common" loader will be used as Catalina's "shared" loader.
# Examples:
# "foo": Add this folder as a class repository
# "foo/*.jar": Add all the JARs of the specified folder as class
# repositories
# "foo/bar.jar": Add bar.jar as a class repository
# Please note that for single jars, e.g. bar.jar, you need the URL form
# starting with file:.
#
# Note: Values may be enclosed in double quotes ("...") in case either the
# ${catalina.base} path or the ${catalina.home} path contains a comma.
# Because double quotes are used for quoting, the double quote character
# may not appear in a path.
shared.loader=
# Default list of JAR files that should not be scanned using the JarScanner
# functionality. This is typically used to scan JARs for configuration
# information. JARs that do not contain such information may be excluded from
# the scan to speed up the scanning process. This is the default list. JARs on
# this list are excluded from all scans. The list must be a comma separated list
# of JAR file names.
# The list of JARs to skip may be over-ridden at a Context level for individual
# scan types by configuring a JarScanner with a nested JarScanFilter.
# The JARs listed below include:
# - Tomcat Bootstrap JARs
# - Tomcat API JARs
# - Catalina JARs
# - Jasper JARs
# - Tomcat JARs
# - Common non-Tomcat JARs
# - Test JARs (JUnit, Cobertura and dependencies)
tomcat.util.scan.StandardJarScanFilter.jarsToSkip=\
annotations-api.jar,\
ant-junit*.jar,\
ant-launcher*.jar,\
ant*.jar,\
asm-*.jar,\
aspectj*.jar,\
bcel*.jar,\
biz.aQute.bnd*.jar,\
bootstrap.jar,\
catalina-ant.jar,\
catalina-ha.jar,\
catalina-ssi.jar,\
catalina-storeconfig.jar,\
catalina-tribes.jar,\
catalina.jar,\
cglib-*.jar,\
cobertura-*.jar,\
commons-beanutils*.jar,\
commons-codec*.jar,\
commons-collections*.jar,\
commons-compress*.jar,\
commons-daemon.jar,\
commons-dbcp*.jar,\
commons-digester*.jar,\
commons-fileupload*.jar,\
commons-httpclient*.jar,\
commons-io*.jar,\
commons-lang*.jar,\
commons-logging*.jar,\
commons-math*.jar,\
commons-pool*.jar,\
derby-*.jar,\
dom4j-*.jar,\
easymock-*.jar,\
ecj-*.jar,\
el-api.jar,\
geronimo-spec-jaxrpc*.jar,\
h2*.jar,\
ha-api-*.jar,\
hamcrest-*.jar,\
hibernate*.jar,\
httpclient*.jar,\
icu4j-*.jar,\
jakartaee-migration-*.jar,\
jasper-el.jar,\
jasper.jar,\
jaspic-api.jar,\
jaxb-*.jar,\
jaxen-*.jar,\
jaxws-rt-*.jar,\
jdom-*.jar,\
jetty-*.jar,\
jmx-tools.jar,\
jmx.jar,\
jsp-api.jar,\
jstl.jar,\
jta*.jar,\
junit-*.jar,\
junit.jar,\
log4j*.jar,\
mail*.jar,\
objenesis-*.jar,\
oraclepki.jar,\
org.hamcrest.core_*.jar,\
org.junit_*.jar,\
oro-*.jar,\
servlet-api-*.jar,\
servlet-api.jar,\
slf4j*.jar,\
taglibs-standard-spec-*.jar,\
tagsoup-*.jar,\
tomcat-api.jar,\
tomcat-coyote.jar,\
tomcat-coyote-ffm.jar,\
tomcat-dbcp.jar,\
tomcat-i18n-*.jar,\
tomcat-jdbc.jar,\
tomcat-jni.jar,\
tomcat-juli-adapters.jar,\
tomcat-juli.jar,\
tomcat-util-scan.jar,\
tomcat-util.jar,\
tomcat-websocket.jar,\
tools.jar,\
unboundid-ldapsdk-*.jar,\
websocket-api.jar,\
websocket-client-api.jar,\
wsdl4j*.jar,\
xercesImpl.jar,\
xml-apis.jar,\
xmlParserAPIs-*.jar,\
xmlParserAPIs.jar,\
xom-*.jar
# Default list of JAR files that should be scanned that overrides the default
# jarsToSkip list above. This is typically used to include a specific JAR that
# has been excluded by a broad file name pattern in the jarsToSkip list.
# The list of JARs to scan may be over-ridden at a Context level for individual
# scan types by configuring a JarScanner with a nested JarScanFilter.
tomcat.util.scan.StandardJarScanFilter.jarsToScan=\
log4j-taglib*.jar,\
log4j-jakarta-web*.jar,\
log4javascript*.jar,\
slf4j-taglib*.jar
# String cache configuration.
tomcat.util.buf.StringCache.byte.enabled=true
#tomcat.util.buf.StringCache.char.enabled=true
#tomcat.util.buf.StringCache.trainThreshold=500000
#tomcat.util.buf.StringCache.cacheSize=5000

View File

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- The contents of this file will be loaded for each web application -->
<Context>
<!-- Default set of monitored resources. If one of these changes, the -->
<!-- web application will be reloaded. -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<WatchedResource>WEB-INF/tomcat-web.xml</WatchedResource>
<WatchedResource>${catalina.base}/conf/web.xml</WatchedResource>
<!-- Uncomment this to enable session persistence across Tomcat restarts -->
<!--
<Manager pathname="SESSIONS.ser" />
-->
</Context>

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<jaspic-providers xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml jaspic-providers.xsd"
version="1.0">
<!-- No JASPIC providers configured by default -->
</jaspic-providers>

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<xs:schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://tomcat.apache.org/xml"
xmlns:jaspic="http://tomcat.apache.org/xml"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.0">
<xs:element name="jaspic-providers">
<xs:complexType>
<xs:sequence>
<xs:element name="provider" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="property" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="name" use="required" type="jaspic:propertyname" />
<xs:attribute name="value" use="required" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="className" type="xs:string" />
<xs:attribute name="layer" type="xs:string" />
<xs:attribute name="appContext" type="xs:string" />
<xs:attribute name="description" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="version" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:simpleType name="propertyname">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>

View File

@ -1,76 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
handlers = 1catalina.org.apache.juli.AsyncFileHandler, 2localhost.org.apache.juli.AsyncFileHandler, 3manager.org.apache.juli.AsyncFileHandler, 4host-manager.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler
.handlers = 1catalina.org.apache.juli.AsyncFileHandler, java.util.logging.ConsoleHandler
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
1catalina.org.apache.juli.AsyncFileHandler.level = ALL
1catalina.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
1catalina.org.apache.juli.AsyncFileHandler.prefix = catalina.
1catalina.org.apache.juli.AsyncFileHandler.maxDays = 90
1catalina.org.apache.juli.AsyncFileHandler.encoding = UTF-8
2localhost.org.apache.juli.AsyncFileHandler.level = ALL
2localhost.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
2localhost.org.apache.juli.AsyncFileHandler.prefix = localhost.
2localhost.org.apache.juli.AsyncFileHandler.maxDays = 90
2localhost.org.apache.juli.AsyncFileHandler.encoding = UTF-8
3manager.org.apache.juli.AsyncFileHandler.level = ALL
3manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
3manager.org.apache.juli.AsyncFileHandler.prefix = manager.
3manager.org.apache.juli.AsyncFileHandler.maxDays = 90
3manager.org.apache.juli.AsyncFileHandler.encoding = UTF-8
4host-manager.org.apache.juli.AsyncFileHandler.level = ALL
4host-manager.org.apache.juli.AsyncFileHandler.directory = ${catalina.base}/logs
4host-manager.org.apache.juli.AsyncFileHandler.prefix = host-manager.
4host-manager.org.apache.juli.AsyncFileHandler.maxDays = 90
4host-manager.org.apache.juli.AsyncFileHandler.encoding = UTF-8
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter
java.util.logging.ConsoleHandler.encoding = UTF-8
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].handlers = 2localhost.org.apache.juli.AsyncFileHandler
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/manager].handlers = 3manager.org.apache.juli.AsyncFileHandler
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].level = INFO
org.apache.catalina.core.ContainerBase.[Catalina].[localhost].[/host-manager].handlers = 4host-manager.org.apache.juli.AsyncFileHandler
# For example, set the org.apache.catalina.util.LifecycleBase logger to log
# each component that extends LifecycleBase changing state:
#org.apache.catalina.util.LifecycleBase.level = FINE
# To see debug messages for HTTP/2 handling, uncomment the following line:
#org.apache.coyote.http2.level = FINE
# To see debug messages for WebSocket handling, uncomment the following line:
#org.apache.tomcat.websocket.level = FINE

View File

@ -1,154 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Note: A "Server" is not itself a "Container", so you may not
define subcomponents such as "Valves" at this level.
Documentation at /docs/config/server.html
-->
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<!-- Security listener. Documentation at /docs/config/listeners.html
<Listener className="org.apache.catalina.security.SecurityListener" />
-->
<!-- OpenSSL support using Tomcat Native -->
<Listener className="org.apache.catalina.core.AprLifecycleListener" />
<!-- OpenSSL support using FFM API from Java 22 -->
<!-- <Listener className="org.apache.catalina.core.OpenSSLLifecycleListener" /> -->
<!-- Prevent memory leaks due to use of particular java/javax APIs-->
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<!-- A "Service" is a collection of one or more "Connectors" that share
a single "Container" Note: A "Service" is not itself a "Container",
so you may not define subcomponents such as "Valves" at this level.
Documentation at /docs/config/service.html
-->
<Service name="Catalina">
<!--The connectors can use a shared executor, you can define one or more named thread pools-->
<!--
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
-->
<!-- A "Connector" represents an endpoint by which requests are received
and responses are returned. Documentation at :
HTTP Connector: /docs/config/http.html
AJP Connector: /docs/config/ajp.html
Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!-- A "Connector" using the shared thread pool-->
<!--
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
-->
<!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
This connector uses the NIO implementation. The default
SSLImplementation will depend on the presence of the APR/native
library and the useOpenSSL attribute of the AprLifecycleListener.
Either JSSE or OpenSSL style configuration may be used regardless of
the SSLImplementation selected. JSSE style configuration is used below.
-->
<!--
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true">
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
certificateKeystorePassword="changeit" type="RSA" />
</SSLHostConfig>
</Connector>
-->
<!-- Define an AJP 1.3 Connector on port 8009 -->
<!--
<Connector protocol="AJP/1.3"
address="::1"
port="8009"
redirectPort="8443" />
-->
<!-- An Engine represents the entry point (within Catalina) that processes
every request. The Engine implementation for Tomcat stand alone
analyzes the HTTP headers included with the request, and passes them
on to the appropriate Host (virtual host).
Documentation at /docs/config/engine.html -->
<!-- You should set jvmRoute to support load-balancing via AJP ie :
<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
-->
<Engine name="Catalina" defaultHost="localhost">
<!--For clustering, please take a look at documentation at:
/docs/cluster-howto.html (simple how to)
/docs/config/cluster.html (reference documentation) -->
<!--
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
-->
<!-- Use the LockOutRealm to prevent attempts to guess user passwords
via a brute-force attack -->
<Realm className="org.apache.catalina.realm.LockOutRealm">
<!-- This Realm uses the UserDatabase configured in the global JNDI
resources under the key "UserDatabase". Any edits
that are performed against this UserDatabase are immediately
available for use by the Realm. -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!-- SingleSignOn valve, share authentication between web applications
Documentation at: /docs/config/valve.html -->
<!--
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
-->
<!-- Access log processes all example.
Documentation at: /docs/config/valve.html
Note: The pattern used is equivalent to using pattern="common" -->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b" />
</Host>
</Engine>
</Service>
</Server>

View File

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<tomcat-users xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
version="1.0">
<!--
By default, no user is included in the "manager-gui" role required
to operate the "/manager/html" web application. If you wish to use this app,
you must define such a user - the username and password are arbitrary.
Built-in Tomcat manager roles:
- manager-gui - allows access to the HTML GUI and the status pages
- manager-script - allows access to the HTTP API and the status pages
- manager-jmx - allows access to the JMX proxy and the status pages
- manager-status - allows access to the status pages only
The users below are wrapped in a comment and are therefore ignored. If you
wish to configure one or more of these users for use with the manager web
application, do not forget to remove the <!.. ..> that surrounds them. You
will also need to set the passwords to something appropriate.
-->
<!--
<user username="admin" password="<must-be-changed>" roles="manager-gui"/>
<user username="robot" password="<must-be-changed>" roles="manager-script"/>
-->
<!--
The sample user and role entries below are intended for use with the
examples web application. They are wrapped in a comment and thus are ignored
when reading this file. If you wish to configure these users for use with the
examples web application, do not forget to remove the <!.. ..> that surrounds
them. You will also need to set the passwords to something appropriate.
-->
<!--
<role rolename="tomcat"/>
<role rolename="role1"/>
<user username="tomcat" password="<must-be-changed>" roles="tomcat"/>
<user username="both" password="<must-be-changed>" roles="tomcat,role1"/>
<user username="role1" password="<must-be-changed>" roles="role1"/>
-->
</tomcat-users>

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<xs:schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://tomcat.apache.org/xml"
xmlns:users="http://tomcat.apache.org/xml"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.0">
<xs:element name="tomcat-users">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="role">
<xs:complexType>
<xs:attribute name="rolename" use="required" type="users:entityname" />
<xs:attribute name="description" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="group">
<xs:complexType>
<xs:attribute name="groupname" use="required" type="users:entityname" />
<xs:attribute name="description" type="xs:string" />
<xs:attribute name="roles" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="user">
<xs:complexType>
<xs:attribute name="username" use="required" type="users:entityname" />
<xs:attribute name="fullname" type="xs:string" />
<xs:attribute name="password" type="xs:string" />
<xs:attribute name="roles" type="xs:string" />
<xs:attribute name="groups" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:choice>
<xs:attribute name="version" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:simpleType name="entityname">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>

File diff suppressed because it is too large Load Diff

110
README.md
View File

@ -1,110 +0,0 @@
# Tinder Clone
Образовательный проект - клон приложения Tinder, реализованный на Java с использованием Jakarta Servlet API.
## Описание проекта
Это веб-приложение, имитирующее функциональность популярного приложения для знакомств Tinder. Проект разработан в учебных целях для демонстрации навыков разработки на Java EE/Jakarta EE.
## Основной функционал
- **Регистрация и аутентификация пользователей**
- **Управление профилями** (создание, просмотр, редактирование)
- **Система ролей** (ADMIN, USER)
- **Мультиязычность** (поддержка русского и английского языков)
- **Фильтрация и безопасность** (AuthFilter, ErrorFilter)
- **Валидация данных**
## Технологический стек
- **Java 17+**
- **Jakarta Servlet API**
- **JSP (JavaServer Pages)**
- **JSTL (Jakarta Standard Tag Library)**
- **Lombok** (для генерации кода)
- **SLF4J** (для логирования)
- **Maven/Gradle** (сборка проекта)
## Архитектура проекта
Проект следует многослойной архитектуре:
```
src/ru/charm/back/
├── controller/ # Контроллеры и фильтры
├── dao/ # Объекты доступа к данным (in-memory storage)
├── dto/ # Объекты передачи данных
├── mapper/ # Мапперы между сущностями
├── model/ # Модели данных и исключения
├── service/ # Бизнес-логика
├── utils/ # Утилитные классы
└── validator/ # Валидаторы
```
## Основные компоненты
### Модели данных
- **Profile** - основная сущность пользователя
- **Gender** - enum (MALE, FEMALE)
- **Role** - enum (ADMIN, USER)
- **Status** - enum (ACTIVE, INACTIVE)
### Контроллеры
- **RegistrationController** - регистрация новых пользователей
- **LoginController** - аутентификация
- **ProfileController** - управление профилями
- **CredentialsController** - управление учетными данными
- **ContentController** - контент приложения
- **LanguageController** - переключение языков
### Фильтры
- **AuthFilter** - фильтр аутентификации
- **ErrorFilter** - обработка ошибок
- **HiddenHttpMethodFilter** - поддержка HTTP методов
- **LanguageFilter** - фильтр языковых настроек
## Установка и запуск
### Требования
- JDK 17 или выше
- Apache Tomcat 10+
- IDE (IntelliJ IDEA рекомендуется)
### Запуск проекта
1. Клонировать репозиторий
2. Открыть проект в IDE
3. Настроить Tomcat Server
4. Развернуть проект на сервере
5. Открыть `http://localhost:8080/Tinder_Clone/` в браузере
## Структура данных
Проект использует in-memory хранилище на основе `ConcurrentHashMap` для хранения профилей пользователей. При запуске создаются два тестовых пользователя:
- **Admin**: ivanov@mail.ru / 123
- **User**: sidorova@mail.ru / 456
## Особенности реализации
- **Singleton паттерн** для DAO и Service слоев
- **Функциональное программирование** с использованием Stream API
- **Безопасность** через фильтры и валидацию
- **Интернационализация** через resource bundles
- **RESTful подход** в контроллерах
## Возможности для развития
- Интеграция с реальной базой данных (PostgreSQL, MySQL)
- Добавление WebSocket для real-time функциональности
- Реализация системы лайков и мэтчей
- Добавление загрузки фотографий
- Интеграция с социальными сетями
- Разработка мобильного приложения
## Автор
Проект разработан в образовательных целях как часть курса по веб-разработке на Java.
## Лицензия
Этот проект предназначен только для образовательных целей.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -1,54 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Email</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<h3 style="color: red">${wordBundle.getWord("email-warning")}</h3>
<table>
<tr class="hiddenRow">
<form method="post" action="/email?id=${profile.id}" enctype="multipart/form-data">
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="id" value="${profile.id}">
<table>
<tr>
<td><h3>${wordBundle.getWord("email")}</h3></td>
<td><input type="email" name="email" value="${profile.email}"></td>
</tr>
</table>
<table>
<tr class="hiddenRow">
<td>
<input type="image" src="content/app/img/floppy-disk.png" width="75" alt="submit"
class="icon"/>
</td>
</tr>
</table>
</form>
</tr>
<tr class="hiddenRow">
<div style="color: red">
<c:forEach var="error" items="${errors}">
<span>${wordBundle.getWord(error)}</span>
<br>
</c:forEach>
</div>
</tr>
<tr class="hiddenRow">
<form method="post" action="/registration">
<input type="hidden" name="_method" value="delete"/>
<input type="hidden" name="id" value="${profile.id}">
<input type="image" src="content/app/img/cross.png" width="75" alt="submit" class="icon"/>
</form>
</tr>
</table>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -1,15 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Unexpected Error</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<h3 style="color: red">500 - ${requestScope.wordBundle.getWord("unexpected-error")}</h3>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -1,15 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Bad Request</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<h3>400 - ${requestScope.wordBundle.getWord("page-bad-request")}</h3>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -1,15 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Not Found</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<h3>404 - ${requestScope.wordBundle.getWord("page-not-found")}</h3>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -1,5 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<div>
<hr>
<small>${requestScope.wordBundle.getWord("all-rights-reserved")} 2024</small>
</div>

View File

@ -1,37 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div>
<table>
<tr class="hiddenRow">
<td>
<a href="/"><img src="content/app/img/heart.png" width="75"></a>
</td>
<td>
<form method="post" action="/lang">
<button type="submit" name="lang" value="ru" class="langButton">
<img src="content/app/img/ru.png" class="langImg">
</button>
</form>
</td>
<td>
<form method="post" action="/lang">
<button type="submit" name="lang" value="en" class="langButton">
<img src="content/app/img/en.png" class="langImg">
</button>
</form>
</td>
<td>
<c:if test="${sessionScope.userDetails == null}">
<a href="/login"><img src="content/app/img/key.png" width="75"></a>
</c:if>
<c:if test="${sessionScope.userDetails != null}">
<form method="post" action="/logout">
<input type="image" src="content/app/img/key.png" width="75" alt="submit" class="icon"
onclick="return confirm()"/>
</form>
</c:if>
</td>
</tr>
</table>
<hr>
</div>

View File

@ -1,43 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Login</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<form method="post" action="/login">
<table>
<tr>
<td><h3>${wordBundle.getWord("email")}</h3></td>
<td><input type="email" name="email" placeholder="user@charm.ru"></td>
</tr>
<tr>
<td><h3>${wordBundle.getWord("password")}</h3></td>
<td><input type="password" name="password"></td>
</tr>
</table>
<table>
<tr class="hiddenRow">
<td>
<a href="/registration"><img src="content/app/img/pencil.png" width="75"></a>
</td>
<td>
<input type="image" src="content/app/img/arrow-right.png" width="75" alt="submit"
class="icon"/>
</td>
</tr>
</table>
</form>
<div style="color: red">
<c:forEach var="error" items="${errors}">
<span>${wordBundle.getWord(error)}</span>
<br>
</c:forEach>
</div>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -1,76 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Profile</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<form method="post" action="/profile?id=${profile.id}" enctype="multipart/form-data">
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="id" value="${profile.id}">
<table>
<tr>
<td><h3>${wordBundle.getWord("name")}</h3></td>
<td><input type="text" name="name" value="${profile.name}"></td>
</tr>
<tr>
<td><h3>${wordBundle.getWord("surname")}</h3></td>
<td><input type="text" name="surname" value="${profile.surname}"></td>
</tr>
<tr>
<td><h3>${wordBundle.getWord("birth-date")}</h3></td>
<td><input type="date" name="birthDate" value="${profile.birthDate}"></td>
</tr>
<tr>
<td><h3>${wordBundle.getWord("about")}</h3></td>
<td><textarea name="about" cols="100" maxlength="1000" rows="5" wrap="soft">${profile.about}</textarea></td>
</tr>
<tr>
<td><h3>${wordBundle.getWord("gender")}</h3></td>
<td>
<select name="gender">
<option value="${profile.gender}" selected hidden>
${wordBundle.getWord(profile.gender)}
</option>
<c:forEach var="gender" items="${applicationScope.genders}">
<option value="${gender}">${wordBundle.getWord(gender)}</option>
</c:forEach>
</select>
</td>
</tr>
<tr>
<td><h3>${wordBundle.getWord("photo")}</h3></td>
<td>
<c:if test="${profile.photo != null}">
<img src="content/profiles/${requestScope.profile.id}/${requestScope.profile.photo}" alt="" height="300">
</c:if>
<br>
<input type="button" value="${wordBundle.getWord('update')}" onclick="document.getElementById('file').click();" />
<input type="file" name="photo" id="file" style="display:none;">
</td>
</tr>
</table>
<table>
<tr class="hiddenRow">
<td>
<input type="image" src="content/app/img/floppy-disk.png" width="75" alt="submit" class="icon" />
</td>
<td>
<a href="/email?id=${profile.id}"><img src="content/app/img/key.png" alt="" width="75"></a>
</td>
</tr>
</table>
</form>
<div style="color: red">
<c:forEach var="error" items="${errors}">
<span>${wordBundle.getWord(error)}</span>
<br>
</c:forEach>
</div>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -1,48 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Profiles</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<table>
<tr>
<td><h3>id</h3></td>
<td><h3>${requestScope.wordBundle.getWord("email")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("name")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("surname")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("age")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("status")}</h3></td>
</tr>
<c:forEach var="profile" items="${requestScope.profiles}">
<tr>
<td><h4>${profile.id}</h4></td>
<td><h4>${profile.email}</h4></td>
<td><h4>${profile.name}</h4></td>
<td><h4>${profile.surname}</h4></td>
<td><h4>${profile.age}</h4></td>
<td>
<form action="/profile" method="post">
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="id" value="${profile.id}">
<select name="status">
<option value="${profile.status}" selected hidden>
${requestScope.wordBundle.getWord(profile.status)}
</option>
<c:forEach var="status" items="${applicationScope.statuses}">
<option value="${status}">${requestScope.wordBundle.getWord(status)}</option>
</c:forEach>
</select>
<button type="submit">${requestScope.wordBundle.getWord("save")}</button>
</form>
</td>
</tr>
</c:forEach>
</table>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -1,35 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Registration</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<form method="post" action="/registration">
<table>
<tr>
<td><h3>${requestScope.wordBundle.getWord("email")}</h3></td>
<td><input type="email" name="email" placeholder="user@charm.ru"></td>
</tr>
<tr>
<td><h3>${requestScope.wordBundle.getWord("password")}</h3></td>
<td><input type="password" name="password"></td>
</tr>
</table>
<button type="submit">${requestScope.wordBundle.getWord("save")}</button>
</form>
<div style="color: red">
<c:forEach var="error" items="${errors}">
<span>${wordBundle.getWord(error)}</span>
<br>
</c:forEach>
</div>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -1,78 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<style>
.icon {
width: 75px;
}
.langButton {
padding: 0px;
border: none;
background: 0;
}
.langImg {
width: 40px;
}
input {
width: 500px;
font-size: 15px;
font-family: "Roboto", sans-serif;
}
select {
width: 500px;
font-size: 15px;
font-family: "Roboto", sans-serif;
}
img {
max-width: 500px;
max-height: 500px;
}
.photo {
max-width: 300px;
max-height: 500px;
}
textarea {
resize: none;
width: 500px;
font-size: 15px;
font-family: "Roboto", sans-serif;
}
div {
text-align: center;
font-family: "Roboto", sans-serif;
}
table {
margin-left: auto;
margin-right: auto;
border-collapse: collapse;
}
td {
text-align: left;
padding: 10px;
}
tr {
text-align: center;
padding: 10px;
border: 1px solid;
border-top: none;
border-left: none;
border-right: none;
}
.hiddenRow {
text-align: center;
padding: 0px;
border: none;
}
</style>
</html>

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/error404.jsp</location>
</error-page>
<error-page>
<error-code>400</error-code>
<location>/WEB-INF/jsp/error400.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/WEB-INF/jsp/error.jsp</location>
</error-page>
</web-app>

View File

@ -1,26 +0,0 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Charm</title>
</head>
<body>
<h3>Hello from Charm!</H3>
</body>
</html>

View File

@ -1,7 +0,0 @@
org.slf4j.simpleLogger.defaultLogLevel=info
org.slf4j.simpleLogger.showDateTime=true
org.slf4j.simpleLogger.dateTimeFormat=dd-MM-yyyy HH:mm:ss:SSS
org.slf4j.simpleLogger.logFile=System.out
org.slf4j.simpleLogger.levelInBrackets=true
org.slf4j.simpleLogger.showLogName=true
org.slf4j.simpleLogger.showShortLogName=false

View File

@ -1,28 +0,0 @@
about=About
active=Active
age=Age
all-rights-reserved=All rights reserved
birth-date=Birth date
confirm-password=Confirm password
delete=Delete
email=Email
email-warning=Changing your mail may have negative effects
error.age.invalid=Age must be between 18 and 100 years old
error.email.exist=This email already exists
error.email.invalid=Invalid email
error.password.invalid=Invalid password
female=Female
gender=Gender
inactive=Inactive
male=Male
name=Name
other=Other
page-bad-request=Bad request
page-not-found=Page not found
password=Password
photo=Photo
save=Save
status=Status
surname=Surname
unexpected-error=Unexpected error
update=Update

View File

@ -1,28 +0,0 @@
about=About
active=Active
age=Age
all-rights-reserved=All rights reserved
birth-date=Birth date
confirm-password=Confirm password
delete=Delete
email=Email
email-warning=Changing your mail may have negative effects
error.age.invalid=Age must be between 18 and 100 years old
error.email.exist=This email already exists
error.email.invalid=Invalid email
error.password.invalid=Invalid password
female=Female
gender=Gender
inactive=Inactive
male=Male
name=Name
other=Other
page-bad-request=Bad request
page-not-found=Page not found
password=Password
photo=Photo
save=Save
status=Status
surname=Surname
unexpected-error=Unexpected error
update=Update

View File

@ -1,28 +0,0 @@
about=\u041E \u0441\u0435\u0431\u0435
active=\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0439
age=\u0412\u043E\u0437\u0440\u0430\u0441\u0442
all-rights-reserved=\u0412\u0441\u0435 \u043F\u0440\u0430\u0432\u0430 \u0437\u0430\u0449\u0438\u0449\u0435\u043D\u044B
birth-date=\u0414\u0430\u0442\u0430 \u0440\u043E\u0436\u0434\u0435\u043D\u0438\u044F
confirm-password=\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u043F\u0430\u0440\u043E\u043B\u044C
delete=\u0423\u0434\u0430\u043B\u0438\u0442\u044C
email=\u041F\u043E\u0447\u0442\u0430
email-warning=\u0421\u043C\u0435\u043D\u0430 \u043F\u043E\u0447\u0442\u044B \u043C\u043E\u0436\u0435\u0442 \u043F\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043A \u043D\u0435\u0436\u0435\u043B\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u043F\u043E\u0441\u043B\u0435\u0434\u0441\u0442\u0432\u0438\u044F\u043C
error.age.invalid=\u0412\u043E\u0437\u0440\u0430\u0441\u0442 \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0442 18 \u0434\u043E 100 \u043B\u0435\u0442
error.email.exist=\u0422\u0430\u043A\u0430\u044F \u043F\u043E\u0447\u0442\u0430 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442
error.email.invalid=\u041D\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u043F\u043E\u0447\u0442\u0430
error.password.invalid=\u041D\u0435\u043F\u043E\u0434\u0445\u043E\u0434\u044F\u0449\u0438\u0439 \u043F\u0430\u0440\u043E\u043B\u044C
female=\u0416\u0435\u043D\u0449\u0438\u043D\u0430
gender=\u041F\u043E\u043B
inactive=\u041D\u0435\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439
male=\u041C\u0443\u0436\u0447\u0438\u043D\u0430
name=\u0418\u043C\u044F
other=\u0414\u0440\u0443\u0433\u043E\u0435
page-bad-request=\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 \u0437\u0430\u043F\u0440\u043E\u0441
page-not-found=\u0421\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430
password=\u041F\u0430\u0440\u043E\u043B\u044C
photo=\u0424\u043E\u0442\u043E
save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C
status=\u0421\u0442\u0430\u0442\u0443\u0441
surname=\u0424\u0430\u043C\u0438\u043B\u0438\u044F
unexpected-error=\u041D\u0435\u043F\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043D\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430
update=\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C

View File

@ -0,0 +1,27 @@
package ru.charm.back;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;
public class CharmBackClientRunner {
public static void main(String[] args) throws IOException {
try (
Socket socket = new Socket("localhost", 8080);
DataInputStream rsStream = new DataInputStream(socket.getInputStream());
DataOutputStream rqStream = new DataOutputStream(socket.getOutputStream());
Scanner scanner = new Scanner(System.in)
) {
while (scanner.hasNextLine()) {
System.out.println("Client: ");
String request = scanner.nextLine();
rqStream.writeUTF(request);
String response = rsStream.readUTF();
System.out.println("Server: " + response);
}
}
}
}

View File

@ -0,0 +1,49 @@
package ru.charm.back;
import ru.charm.back.controller.ProfileController;
import ru.charm.back.dao.ProfileDao;
import ru.charm.back.model.Command;
import ru.charm.back.service.ProfileService;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class CharmBackServerRunner {
public static void main(String[] args) throws IOException {
ProfileController controller = new ProfileController(new ProfileService(new ProfileDao()));
try (
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
DataInputStream rqStream = new DataInputStream(socket.getInputStream());
DataOutputStream rsStream = new DataOutputStream(socket.getOutputStream());
) {
String request = rqStream.readUTF();
String response;
while (!request.equals("stop")) {
if (request.startsWith(Command.SAVE.getPrefix())) {
response = controller.save(request.split("save ")[1]);
} else if (request.startsWith(Command.FIND_BY_ID.getPrefix())) {
response = controller.findById(request.split("findById ")[1]);
} else if (request.startsWith(Command.FIND_ALL.getPrefix())) {
response = controller.findAll();
} else if (request.startsWith(Command.UPDATE.getPrefix())) {
response = controller.update(request.split("update ")[1]);
} else if (request.startsWith(Command.DELETE.getPrefix())) {
response = controller.delete(request.split("delete ")[1]);
} else {
response = "Unsupported method";
}
rsStream.writeUTF(response);
request = rqStream.readUTF();
System.out.println("Server: " + request);
}
}
}
}

View File

@ -1,34 +0,0 @@
package ru.charm.back.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import ru.charm.back.service.ContentService;
import java.io.IOException;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
@WebServlet("/content/*")
@Slf4j
public class ContentController extends HttpServlet {
private final ContentService service = ContentService.getInstance();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
String contentPath = req.getRequestURI().replace("/content", "");
log.info("Content download request: {} {}", req.getRequestURI(), contentPath);
resp.setContentType("application/octet-stream");
service.download(contentPath, resp.getOutputStream());
log.info("Content successfully downloaded: {}", req);
} catch (IOException e) {
log.error("Content download failed: {}", e.getMessage());
resp.sendError(SC_NOT_FOUND);
}
}
}

View File

@ -1,54 +0,0 @@
package ru.charm.back.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.mapper.RequestToProfileUpdateDtoMapper;
import ru.charm.back.model.exception.DuplicateEmailException;
import ru.charm.back.service.ProfileService;
import java.io.IOException;
import java.util.Optional;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
@WebServlet("/credentials")
@Slf4j
@MultipartConfig
public class CredentialsController extends HttpServlet {
private final ProfileService service = ProfileService.getInstance();
private final RequestToProfileUpdateDtoMapper requestToProfileUpdateDtoMapper = RequestToProfileUpdateDtoMapper.getInstance();
@Override
protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String sId = req.getParameter("id");
String forwardUri = "/notFound";
if (sId != null) {
Optional<ProfileGetDto> optProfileGetDto = service.findById(Long.parseLong(sId));
if (optProfileGetDto.isPresent()) {
req.setAttribute("profile", optProfileGetDto.get());
forwardUri = "/WEB-INF/jsp/email.jsp";
}
}
req.getRequestDispatcher(forwardUri).forward(req, resp);
}
@Override
protected void doPut (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ProfileUpdateDto dto = requestToProfileUpdateDtoMapper.map(req, new ProfileUpdateDto());
try {
service.update(dto);
log.info("Email {} was changed in profile with id = {}", dto.getEmail(), dto.getId());
resp.sendRedirect(String.format("/profile?id=%s", dto.getId()));
} catch (DuplicateEmailException e) {
resp.sendError(SC_BAD_REQUEST);
}
}
}

View File

@ -1,19 +0,0 @@
package ru.charm.back.controller;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/forward")
public class ForwardController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.sendRedirect("https://ya.ru");
}
}

View File

@ -1,31 +0,0 @@
package ru.charm.back.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/lang")
public class LanguageController extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String lang = req.getParameter("lang");
System.out.println("Cookie header" + req.getHeader("Cookie"));
Cookie cookie = new Cookie("lang", "en");
if ("ru".equals(lang)) {
cookie.setValue("ru");
}
resp.addCookie(cookie);
String referer = req.getHeader("Referer");
resp.sendRedirect(referer);
}
}

View File

@ -1,46 +0,0 @@
package ru.charm.back.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import ru.charm.back.dto.LoginDto;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.mapper.RequestToLoginDtoMapper;
import ru.charm.back.mapper.RequestToRegistrationDtoMapper;
import ru.charm.back.service.ProfileService;
import ru.charm.back.validator.RegistrationValidator;
import ru.charm.back.validator.ValidationResult;
import java.io.IOException;
import java.util.Optional;
@WebServlet("/login")
@Slf4j
public class LoginController extends HttpServlet {
private final ProfileService service = ProfileService.getInstance();
private final RequestToLoginDtoMapper requestTologinDtoMapper = RequestToLoginDtoMapper.getInstance();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
LoginDto dto = requestTologinDtoMapper.map(req);
Optional<ProfileGetDto> userDetailOpt = service.login(dto);
if (userDetailOpt.isPresent()) {
req.getSession().setAttribute("userDetails", userDetailOpt.get());
resp.sendRedirect(String.format("/profile?id=%s", userDetailOpt.get().getId()));
return;
}
resp.sendRedirect("/login");
}
}

View File

@ -1,24 +0,0 @@
package ru.charm.back.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import ru.charm.back.dto.LoginDto;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.mapper.RequestToLoginDtoMapper;
import ru.charm.back.service.ProfileService;
import java.io.IOException;
import java.util.Optional;
@WebServlet("/logout")
public class LogoutController extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
req.getSession().invalidate();
resp.sendRedirect("/login");
}
}

View File

@ -1,55 +1,112 @@
package ru.charm.back.controller; package ru.charm.back.controller;
import jakarta.servlet.ServletException; import ru.charm.back.model.Profile;
import jakarta.servlet.annotation.MultipartConfig;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.mapper.RequestToProfileUpdateDtoMapper;
import ru.charm.back.service.ProfileService; import ru.charm.back.service.ProfileService;
import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND; public class ProfileController {
private final ProfileService service;
@WebServlet("/profile") public ProfileController(ProfileService service) {
@MultipartConfig this.service = service;
public class ProfileController extends HttpServlet {
private final ProfileService service = ProfileService.getInstance();
private final RequestToProfileUpdateDtoMapper requestToProfileUpdateDtoMapper = RequestToProfileUpdateDtoMapper.getInstance();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String sId = req.getParameter("id");
String forwardUri = null;
if (sId != null) {
Optional<ProfileGetDto> optProfileDto = service.findById(Long.parseLong(sId));
System.out.println("Optional profile" + optProfileDto.toString());
if (optProfileDto.isPresent()) {
req.setAttribute("profile", optProfileDto.get());
forwardUri = "/WEB-INF/jsp/profile.jsp";
}
} else {
req.setAttribute("profiles", service.findAll());
forwardUri = "/WEB-INF/jsp/profiles.jsp";
}
if (forwardUri == null) {
resp.sendError(SC_NOT_FOUND);
} else {
req.getRequestDispatcher(forwardUri).forward(req, resp);
}
} }
@Override public String work(String request) {
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException { return "";
ProfileUpdateDto dto = requestToProfileUpdateDtoMapper.map(req);
service.update(dto);
String referer = req.getHeader("referer");
resp.sendRedirect(referer);
} }
}
public String save(String save) {
String[] params = save.split(",");
if (params.length != 4) {
return "Bad request";
}
Profile profile = new Profile();
profile.setEmail(params[0]);
profile.setName(params[1]);
profile.setSurname(params[2]);
profile.setAbout(params[3]);
return service.save(profile).toString();
}
public String findById(String request) {
String[] params = request.split(",");
if (params.length != 1) {
return "Bad request";
}
long id;
try {
id = Long.parseLong(params[0]);
} catch (NumberFormatException e) {
return "Bad request";
}
Optional<Profile> maybeProfile = service.findById(id);
if (maybeProfile.isEmpty()) {
return "Not found";
}
return maybeProfile.get().toString();
}
public String findAll() {
return service.findAll().toString();
}
public String update(String request) {
String[] params = request.split(",");
if (params.length != 5) {
return "Bad request";
}
long id;
try {
id = Long.parseLong(params[0]);
} catch (NumberFormatException e) {
return "Bad request";
}
Profile profile = new Profile();
profile.setId(id);
profile.setEmail(params[1]);
profile.setName(params[2]);
profile.setSurname(params[3]);
profile.setAbout(params[4]);
service.update(profile);
return "Update success\n" + profile.toString();
}
public String delete(String request) {
String[] params = request.split(",");
if (params.length != 1) {
return "Bad request";
}
long id;
try {
id = Long.parseLong(params[0]);
} catch (NumberFormatException e) {
return "Bad request";
}
Optional<Profile> maybeProfile = service.findById(id);
if (maybeProfile.isEmpty()) {
return "Not found";
}
service.delete(id);
return "Delete success";
}
}

View File

@ -1,59 +0,0 @@
package ru.charm.back.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.mapper.RequestToRegistrationDtoMapper;
import ru.charm.back.service.ProfileService;
import ru.charm.back.validator.RegistrationValidator;
import ru.charm.back.validator.ValidationResult;
import java.io.IOException;
@WebServlet("/registration")
@Slf4j
public class RegistrationController extends HttpServlet {
private final ProfileService service = ProfileService.getInstance();
private final RequestToRegistrationDtoMapper requestToRegistrationDtoMapper = RequestToRegistrationDtoMapper.getInstance();
private final RegistrationValidator validator = RegistrationValidator.getInstance();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.getRequestDispatcher("/WEB-INF/jsp/registration.jsp").forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
RegistrationDto dto = requestToRegistrationDtoMapper.map(req);
ValidationResult validationResult = validator.validate(dto);
if (!validationResult.isValid()) {
req.setAttribute("errors", validationResult.getErrors());
doGet(req, resp);
return;
}
Long id = service.save(dto);
log.info("Profile with the email address {} has been registered with id {}", dto.getEmail(), id);
resp.sendRedirect(String.format("/profile?id=%s", id));
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String sId = req.getParameter("id");
boolean success = false;
if (!sId.isBlank()) {
success = service.delete(Long.parseLong(sId));
}
resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
if (success) {
log.info("Profile with id {} has been deleted", sId);
}
resp.sendRedirect("/registration");
}
}

View File

@ -1,43 +0,0 @@
package ru.charm.back.controller.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.model.Role;
import ru.charm.back.service.WordBundle;
import java.io.IOException;
import java.util.Arrays;
import java.util.Set;
@WebFilter("/*")
@Slf4j
public class AuthFilter implements Filter {
private final Set<String> PRIVATE_PATHS = Set.of("/profile", "/credentials");
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse res = (HttpServletResponse) servletResponse;
if (PRIVATE_PATHS.contains(req.getRequestURI())) {
ProfileGetDto userDetails = (ProfileGetDto) req.getSession().getAttribute("userDetails");
log.info("User {} is trying to access {}", userDetails, req.getRequestURI());
if (userDetails == null) {
res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} else if (userDetails.getRole() == Role.ADMIN || userDetails.getId().toString().equals( req.getParameter("id"))) {
filterChain.doFilter(req, res);
} else {
res.sendError(HttpServletResponse.SC_FORBIDDEN);
}
} else {
filterChain.doFilter(req, res);
}
}
}

View File

@ -1,31 +0,0 @@
package ru.charm.back.controller.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import static jakarta.servlet.RequestDispatcher.ERROR_EXCEPTION;
@WebFilter(value = "/*", dispatcherTypes = DispatcherType.ERROR)
@Slf4j
public class ErrorFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse res = (HttpServletResponse) servletResponse;
Throwable throwable = (Throwable) req.getAttribute(ERROR_EXCEPTION);
if (res.getStatus() >= 500) {
log.error("Error 500", throwable);
} else {
log.warn("Exception code {}", res.getStatus());
}
filterChain.doFilter(servletRequest, servletResponse);
}
}

View File

@ -1,53 +0,0 @@
package ru.charm.back.controller.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Getter;
import lombok.Setter;
import ru.charm.back.model.Gender;
import java.io.IOException;
import java.util.Locale;
@WebFilter("/*")
public class HiddenHttpMethodFilter implements Filter {
public static final String METHOD_PARAM = "_method";
@Override
public void init(FilterConfig filterConfig) {
ServletContext servletContext = filterConfig.getServletContext();
if (servletContext.getAttribute("genders") == null) {
servletContext.setAttribute("genders", Gender.values());
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String paramValue = request.getParameter(METHOD_PARAM);
if ("POST".equals(request.getMethod()) && paramValue != null && !paramValue.isBlank()) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
filterChain.doFilter(wrapper, response);
} else {
filterChain.doFilter(request, response);
}
}
public static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
@Setter
@Getter
public final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
}
}

View File

@ -1,34 +0,0 @@
package ru.charm.back.controller.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import ru.charm.back.service.WordBundle;
import java.io.IOException;
import java.util.Arrays;
@WebFilter("/*")
public class LanguageFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse res = (HttpServletResponse) servletResponse;
Cookie[] cookies = req.getCookies() != null ? req.getCookies() : new Cookie[]{};
String lang = Arrays.stream(cookies)
.filter(cookie -> "lang".equals(cookie.getName()))
.map(Cookie::getValue)
.findFirst()
.orElse("en");
WordBundle wordBundle = new WordBundle(lang);
req.setAttribute("wordBundle", wordBundle);
filterChain.doFilter(req, res);
}
}

View File

@ -1,92 +1,47 @@
package ru.charm.back.dao; package ru.charm.back.dao;
import ru.charm.back.model.Gender;
import ru.charm.back.model.Profile; import ru.charm.back.model.Profile;
import ru.charm.back.model.Role;
import ru.charm.back.model.Status;
import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public class ProfileDao { public class ProfileDao {
private static final ProfileDao INSTANCE = new ProfileDao();
private final AtomicLong idStorage;
private final ConcurrentHashMap<Long, Profile> storage; private final ConcurrentHashMap<Long, Profile> storage;
private final AtomicLong idStorage;
private ProfileDao() { public ProfileDao() {
this.storage = new ConcurrentHashMap<>(); this.storage = new ConcurrentHashMap<>();
Profile profile = new Profile(); this.idStorage = new AtomicLong();
profile.setId(1L);
profile.setEmail("ivanov@mail.ru");
profile.setPassword("123");
profile.setName("Ivan");
profile.setSurname("Ivanov");
profile.setBirthDate(LocalDate.parse("2001-12-03"));
profile.setAbout("I am QA");
profile.setGender(Gender.MALE);
profile.setStatus(Status.ACTIVE);
profile.setRole(Role.ADMIN);
this.storage.put(1L, profile);
Profile profile1 = new Profile();
profile1.setId(2L);
profile1.setEmail("sidorova@mail.ru");
profile1.setPassword("456");
profile1.setName("Elena");
profile1.setSurname("Sidorova");
profile1.setBirthDate(LocalDate.parse("1999-09-01"));
profile1.setAbout("I am Java Dev");
profile1.setGender(Gender.FEMALE);
profile1.setStatus(Status.INACTIVE);
profile1.setRole(Role.USER);
this.storage.put(2L, profile1);
this.idStorage = new AtomicLong(3L);
}
public static ProfileDao getInstance() {
return INSTANCE;
} }
public Profile save(Profile profile) { public Profile save(Profile profile) {
profile.setId(idStorage.getAndIncrement()); long id = idStorage.incrementAndGet();
storage.put(profile.getId(), profile); profile.setId(id);
storage.put(id, profile);
return profile; return profile;
} }
public Optional<Profile> findById(Long id) { public Optional<Profile> findById(Long id) {
if (id == null) return Optional.empty();
return Optional.ofNullable(storage.get(id)); return Optional.ofNullable(storage.get(id));
} }
public void update(Profile profile) {
Long id = profile.getId();
if (id == null) {
return;
}
storage.put(id, profile);
}
public List<Profile> findAll() { public List<Profile> findAll() {
return new ArrayList<>(storage.values()); return new ArrayList<>(storage.values());
} }
public void update(Profile profile) { public Boolean delete(Long id) {
Long id = profile.getId();
if (id == null) return;
storage.put(id, profile);
}
public boolean delete(Long id) {
if (id == null) return false;
return storage.remove(id) != null; return storage.remove(id) != null;
} }
public Set<String> getAllEmails() {
return storage.values().stream().map(Profile::getEmail).collect(Collectors.toSet());
}
public Optional<Profile> findByEmail(String email) {
if (email == null) return Optional.empty();
return storage.values().stream().filter(profile -> profile.getEmail().equals(email)).findFirst();
}
} }

View File

@ -1,9 +0,0 @@
package ru.charm.back.dto;
import lombok.Data;
@Data
public class LoginDto {
private String email;
private String password;
}

View File

@ -1,26 +0,0 @@
package ru.charm.back.dto;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import ru.charm.back.model.Gender;
import ru.charm.back.model.Role;
import ru.charm.back.model.Status;
import java.time.LocalDate;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ProfileGetDto {
Long id;
String email;
String name;
String surname;
LocalDate birthDate;
Integer age;
String about;
Gender gender;
Status status;
String photo;
Role role;
}

View File

@ -1,23 +0,0 @@
package ru.charm.back.dto;
import jakarta.servlet.http.Part;
import lombok.Data;
import ru.charm.back.model.Gender;
import ru.charm.back.model.Status;
import java.time.LocalDate;
@Data
public class ProfileUpdateDto {
private Long id;
private String email;
private String password;
private String name;
private String surname;
private LocalDate birthDate;
private String about;
private Gender gender;
private Status status;
private Part photo;
}

View File

@ -1,9 +0,0 @@
package ru.charm.back.dto;
import lombok.Data;
@Data
public class RegistrationDto {
private String email;
private String password;
}

View File

@ -1,7 +0,0 @@
package ru.charm.back.mapper;
public interface Mapper<From, To> {
To map(From from);
To map(From from, To to);
}

View File

@ -1,43 +0,0 @@
package ru.charm.back.mapper;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.model.Profile;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class ProfileToProfileGetDtoMapper implements Mapper<Profile, ProfileGetDto> {
private static final ProfileToProfileGetDtoMapper INSTANCE = new ProfileToProfileGetDtoMapper();
private ProfileToProfileGetDtoMapper() {
}
public static ProfileToProfileGetDtoMapper getInstance() {
return INSTANCE;
}
@Override
public ProfileGetDto map(Profile profile) {
return map(profile, new ProfileGetDto());
}
@Override
public ProfileGetDto map(Profile profile, ProfileGetDto dto) {
dto.setId(profile.getId());
dto.setEmail(profile.getEmail());
dto.setName(profile.getName());
dto.setSurname(profile.getSurname());
dto.setBirthDate(profile.getBirthDate());
if (profile.getBirthDate() != null) {
dto.setAge(Math.toIntExact(ChronoUnit.YEARS.between(profile.getBirthDate(), LocalDate.now())));
}
dto.setAbout(profile.getAbout());
dto.setGender(profile.getGender());
dto.setStatus(profile.getStatus());
dto.setPhoto(profile.getPhoto());
dto.setRole(profile.getRole());
return dto;
}
}

View File

@ -1,52 +0,0 @@
package ru.charm.back.mapper;
import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.model.Profile;
public class ProfileUpdateDtoToProfileMapper implements Mapper<ProfileUpdateDto, Profile> {
private static final ProfileUpdateDtoToProfileMapper INSTANCE = new ProfileUpdateDtoToProfileMapper();
private ProfileUpdateDtoToProfileMapper() {
}
public static ProfileUpdateDtoToProfileMapper getInstance() {
return INSTANCE;
}
@Override
public Profile map(ProfileUpdateDto dto) {
return map(dto, new Profile());
}
@Override
public Profile map(ProfileUpdateDto dto, Profile profile) {
profile.setId(dto.getId());
if (dto.getEmail() != null) {
profile.setEmail(dto.getEmail());
}
if (dto.getName() != null) {
profile.setName(dto.getName());
}
if (dto.getSurname() != null) {
profile.setSurname(dto.getSurname());
}
if (dto.getBirthDate() != null) {
profile.setBirthDate(dto.getBirthDate());
}
if (dto.getAbout() != null) {
profile.setAbout(dto.getAbout());
}
if (dto.getGender() != null) {
profile.setGender(dto.getGender());
}
if (dto.getStatus() != null) {
profile.setStatus(dto.getStatus());
}
if (dto.getPhoto() != null) {
profile.setPhoto(dto.getPhoto().getSubmittedFileName());
}
return profile;
}
}

View File

@ -1,32 +0,0 @@
package ru.charm.back.mapper;
import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.model.Profile;
import ru.charm.back.model.Role;
import ru.charm.back.model.Status;
public class RegistrationDtoToProfileMapper implements Mapper<RegistrationDto, Profile> {
private static final RegistrationDtoToProfileMapper INSTANCE = new RegistrationDtoToProfileMapper();
private RegistrationDtoToProfileMapper() {
}
public static RegistrationDtoToProfileMapper getInstance() {
return INSTANCE;
}
@Override
public Profile map(RegistrationDto dto) {
return map(dto, new Profile());
}
@Override
public Profile map(RegistrationDto dto, Profile profile) {
profile.setEmail(dto.getEmail());
profile.setPassword(dto.getPassword());
profile.setStatus(Status.INACTIVE);
profile.setRole(Role.USER);
return profile;
}
}

View File

@ -1,29 +0,0 @@
package ru.charm.back.mapper;
import jakarta.servlet.http.HttpServletRequest;
import ru.charm.back.dto.LoginDto;
import ru.charm.back.dto.RegistrationDto;
public class RequestToLoginDtoMapper implements Mapper<HttpServletRequest, LoginDto> {
private static final RequestToLoginDtoMapper INSTANCE = new RequestToLoginDtoMapper();
private RequestToLoginDtoMapper() {
}
public static RequestToLoginDtoMapper getInstance() {
return INSTANCE;
}
@Override
public LoginDto map(HttpServletRequest req) {
return map(req, new LoginDto());
}
@Override
public LoginDto map(HttpServletRequest req, LoginDto dto) {
dto.setEmail(req.getParameter("email"));
dto.setPassword(req.getParameter("password"));
return dto;
}
}

View File

@ -1,57 +0,0 @@
package ru.charm.back.mapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.model.Gender;
import ru.charm.back.model.Status;
import java.time.LocalDate;
import static ru.charm.back.utils.StringUtils.isBlank;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RequestToProfileUpdateDtoMapper implements Mapper<HttpServletRequest, ProfileUpdateDto> {
private static final RequestToProfileUpdateDtoMapper INSTANCE = new RequestToProfileUpdateDtoMapper();
public static RequestToProfileUpdateDtoMapper getInstance() {
return INSTANCE;
}
@Override
public ProfileUpdateDto map(HttpServletRequest req) {
return map(req, new ProfileUpdateDto());
}
@SneakyThrows
@Override
public ProfileUpdateDto map(HttpServletRequest req, ProfileUpdateDto dto) {
String id = req.getParameter("id");
if (!isBlank(id)) {
dto.setId(Long.parseLong(id));
}
String email = req.getParameter("email");
if (!isBlank(email)) {
dto.setEmail(email);
}
dto.setName(req.getParameter("name"));
dto.setSurname(req.getParameter("surname"));
String birthDate = req.getParameter("birthDate");
if (!isBlank(birthDate)) {
dto.setBirthDate(LocalDate.parse(birthDate));
}
dto.setAbout(req.getParameter("about"));
String gender = req.getParameter("gender");
if (!isBlank(gender)) {
dto.setGender(Gender.valueOf(gender));
}
String status = req.getParameter("status");
if (!isBlank(status)) {
dto.setStatus(Status.valueOf(status));
}
dto.setPhoto(req.getPart("photo"));
return dto;
}
}

View File

@ -1,28 +0,0 @@
package ru.charm.back.mapper;
import jakarta.servlet.http.HttpServletRequest;
import ru.charm.back.dto.RegistrationDto;
public class RequestToRegistrationDtoMapper implements Mapper<HttpServletRequest, RegistrationDto> {
private static final RequestToRegistrationDtoMapper INSTANCE = new RequestToRegistrationDtoMapper();
private RequestToRegistrationDtoMapper() {
}
public static RequestToRegistrationDtoMapper getInstance() {
return INSTANCE;
}
@Override
public RegistrationDto map(HttpServletRequest req) {
return map(req, new RegistrationDto());
}
@Override
public RegistrationDto map(HttpServletRequest req, RegistrationDto dto) {
dto.setEmail(req.getParameter("email"));
dto.setPassword(req.getParameter("password"));
return dto;
}
}

View File

@ -1,6 +0,0 @@
package ru.charm.back.model;
public enum Gender {
MALE,
FEMALE
}

View File

@ -1,21 +1,60 @@
package ru.charm.back.model; package ru.charm.back.model;
import jakarta.servlet.http.Part;
import lombok.Data;
import java.time.LocalDate;
@Data
public class Profile { public class Profile {
private Long id; private Long id;
private String email; private String email;
private String password;
private String name; private String name;
private String surname; private String surname;
private LocalDate birthDate;
private String about; private String about;
private Gender gender;
private Status status; public Long getId() {
private String photo; return id;
private Role role; }
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getAbout() {
return about;
}
public void setAbout(String about) {
this.about = about;
}
@Override
public String toString() {
return "Profile{" +
"id=" + id +
", email='" + email + '\'' +
", name='" + name + '\'' +
", surname='" + surname + '\'' +
", about='" + about + '\'' +
'}';
}
}

View File

@ -1,6 +0,0 @@
package ru.charm.back.model;
public enum Role {
ADMIN,
USER
}

View File

@ -1,5 +0,0 @@
package ru.charm.back.model;
public enum Status {
ACTIVE, INACTIVE
}

View File

@ -1,4 +0,0 @@
package ru.charm.back.model.exception;
public class DuplicateEmailException extends RuntimeException {
}

View File

@ -1,66 +0,0 @@
package ru.charm.back.service;
import jakarta.servlet.ServletOutputStream;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public class ContentService {
private static final ContentService INSTANCE = new ContentService();
public static ContentService getInstance() {
return INSTANCE;
}
private final static String BASE_PATH = "/Users/ksv741/Project/education/YouTube/tinder-clone/Tinder Clone/media";
public void upload(String contentPath, InputStream inputStream) throws IOException {
Path contentFullPath = getAbsolutePath(contentPath);
OutputStream outputStream = Files.exists(contentFullPath.getParent())
? Files.newOutputStream(contentFullPath, CREATE, TRUNCATE_EXISTING)
: null;
if (outputStream == null) {
throw new FileNotFoundException();
}
writeContent(inputStream, outputStream);
}
public void download(String contentPath, ServletOutputStream outputStream) throws IOException {
InputStream inputStream;
if (contentPath.startsWith("/app/")) {
String appPath = "/WEB-INF" + contentPath.replaceFirst("/app", "");
inputStream = ContentService.class.getClassLoader().getResourceAsStream(appPath);
} else {
Path contentFullPath = getAbsolutePath(contentPath);
inputStream = Files.exists(contentFullPath) ? Files.newInputStream(contentFullPath) : null;
}
if (inputStream == null) {
throw new FileNotFoundException();
}
writeContent(inputStream, outputStream);
}
private void writeContent(InputStream inputStream, OutputStream outputStream) throws IOException {
try (inputStream; outputStream) {
int currentByte;
while ((currentByte = inputStream.read()) != -1) {
outputStream.write(currentByte);
}
}
}
private Path getAbsolutePath(String contentPath) {
return Path.of(BASE_PATH, contentPath);
}
}

View File

@ -1,76 +1,43 @@
package ru.charm.back.service; package ru.charm.back.service;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import ru.charm.back.dao.ProfileDao; import ru.charm.back.dao.ProfileDao;
import ru.charm.back.dto.LoginDto;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.mapper.ProfileToProfileGetDtoMapper;
import ru.charm.back.mapper.ProfileUpdateDtoToProfileMapper;
import ru.charm.back.mapper.RegistrationDtoToProfileMapper;
import ru.charm.back.model.Profile; import ru.charm.back.model.Profile;
import ru.charm.back.model.exception.DuplicateEmailException;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ProfileService { public class ProfileService {
private ProfileDao dao;
private static final ProfileService INSTANCE = new ProfileService(); public ProfileService(ProfileDao dao) {
this.dao = dao;
private final ProfileDao dao = ProfileDao.getInstance();
private final ContentService contentService = ContentService.getInstance();
private final ProfileToProfileGetDtoMapper profileToProfileGetDtoMapper = ProfileToProfileGetDtoMapper.getInstance();
private final ProfileUpdateDtoToProfileMapper profileUpdateDtoToProfileMapper = ProfileUpdateDtoToProfileMapper.getInstance();
private final RegistrationDtoToProfileMapper registrationDtoToProfileMapper = RegistrationDtoToProfileMapper.getInstance();
public static ProfileService getInstance() {
return INSTANCE;
} }
public Long save(RegistrationDto dto) { public Profile save(Profile profile) {
return dao.save(registrationDtoToProfileMapper.map(dto)).getId(); return dao.save(profile);
} }
public Optional<ProfileGetDto> findById(Long id) { public Optional<Profile> findById(Long id) {
return dao.findById(id).map(profileToProfileGetDtoMapper::map); if (id == null) {
} return Optional.empty();
public List<ProfileGetDto> findAll() {
return dao.findAll().stream().map(profileToProfileGetDtoMapper::map).toList();
}
@SneakyThrows
public void update(ProfileUpdateDto dto) {
Optional<Profile> optionalProfile = dao.findById(dto.getId());
if (optionalProfile.isPresent()) {
if (dto.getPhoto() != null) {
contentService.upload(
"/profiles/" + dto.getId() + "/" + dto.getPhoto().getSubmittedFileName(),
dto.getPhoto().getInputStream()
);
}
dao.update(profileUpdateDtoToProfileMapper.map(dto, optionalProfile.get()));
} }
return dao.findById(id);
} }
public boolean delete(Long id) { public Boolean delete(Long id) {
if (id == null) {
return false;
}
return dao.delete(id); return dao.delete(id);
} }
public Optional<ProfileGetDto> login(LoginDto dto) { public void update(Profile profile) {
return dao.findByEmail(dto.getEmail()) dao.update(profile);
.filter(profile -> profile.getPassword().equals(dto.getPassword()))
.map(profileToProfileGetDtoMapper::map);
} }
}
public List<Profile> findAll() {
return dao.findAll();
}
}

View File

@ -1,32 +0,0 @@
package ru.charm.back.service;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
public class WordBundle {
private final ResourceBundle resourceBundle;
public WordBundle(String lang) {
Locale locale = Locale.of("en");
if ("ru".equals(lang)) {
locale = Locale.of("ru");
}
this.resourceBundle = ResourceBundle.getBundle("words", locale);
}
public String getWord(String key) {
String result;
try {
result = resourceBundle.getString(key.toLowerCase());
} catch (MissingResourceException | ClassCastException e) {
String[] pathArray = key.split("\\.");
result = pathArray[pathArray.length - 1];
} catch (Exception e) {
return "* empty *";
}
return result;
}
}

View File

@ -1,15 +0,0 @@
package ru.charm.back.utils;
import lombok.experimental.UtilityClass;
import java.util.regex.Pattern;
@UtilityClass
public class StringUtils {
public static final Pattern VALID_EMAIL_ADDRESS_REGEX =
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
public static boolean isBlank(String str) {
return str == null || str.isBlank();
}
}

View File

@ -1,35 +0,0 @@
package ru.charm.back.validator;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import ru.charm.back.dao.ProfileDao;
import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.mapper.ProfileUpdateDtoToProfileMapper;
import static ru.charm.back.utils.StringUtils.VALID_EMAIL_ADDRESS_REGEX;
import static ru.charm.back.utils.StringUtils.isBlank;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RegistrationValidator implements Validator<RegistrationDto> {
private static final RegistrationValidator INSTANCE = new RegistrationValidator();
public static RegistrationValidator getInstance() { return INSTANCE; }
private final ProfileDao dao = ProfileDao.getInstance();
@Override
public ValidationResult validate(RegistrationDto dto) {
ValidationResult validationResult = new ValidationResult();
if (isBlank(dto.getEmail()) || !VALID_EMAIL_ADDRESS_REGEX.matcher(dto.getEmail()).matches()) {
validationResult.addError("error.email.invalid");
} else if (dao.getAllEmails().contains(dto.getEmail())) {
validationResult.addError("error.email.exists");
}
if (isBlank(dto.getPassword())) {
validationResult.addError("error.password.invalid");
}
return validationResult;
}
}

View File

@ -1,20 +0,0 @@
package ru.charm.back.validator;
import java.util.ArrayList;
import java.util.List;
public class ValidationResult {
private final List<String> errors = new ArrayList<>();
public boolean isValid() {
return errors.isEmpty();
}
public void addError(String code) {
errors.add(code);
}
public List<String> getErrors() {
return new ArrayList<>(errors);
}
}

View File

@ -1,5 +0,0 @@
package ru.charm.back.validator;
public interface Validator<T> {
ValidationResult validate(T object);
}