Lopera Mendizabal
1 month ago
commit
45da086353
49 changed files with 2976 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||
{ |
|||
"java.compile.nullAnalysis.mode": "disabled" |
|||
} |
@ -0,0 +1,24 @@ |
|||
target/ |
|||
!.mvn/wrapper/maven-wrapper.jar |
|||
|
|||
### STS ### |
|||
.apt_generated |
|||
.classpath |
|||
.factorypath |
|||
.project |
|||
.settings |
|||
.springBeans |
|||
|
|||
### IntelliJ IDEA ### |
|||
.idea |
|||
*.iws |
|||
*.iml |
|||
*.ipr |
|||
|
|||
### NetBeans ### |
|||
nbproject/private/ |
|||
build/ |
|||
nbbuild/ |
|||
dist/ |
|||
nbdist/ |
|||
.nb-gradle/ |
Binary file not shown.
@ -0,0 +1 @@ |
|||
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip |
@ -0,0 +1,674 @@ |
|||
GNU GENERAL PUBLIC LICENSE |
|||
Version 3, 29 June 2007 |
|||
|
|||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
|||
Everyone is permitted to copy and distribute verbatim copies |
|||
of this license document, but changing it is not allowed. |
|||
|
|||
Preamble |
|||
|
|||
The GNU General Public License is a free, copyleft license for |
|||
software and other kinds of works. |
|||
|
|||
The licenses for most software and other practical works are designed |
|||
to take away your freedom to share and change the works. By contrast, |
|||
the GNU General Public License is intended to guarantee your freedom to |
|||
share and change all versions of a program--to make sure it remains free |
|||
software for all its users. We, the Free Software Foundation, use the |
|||
GNU General Public License for most of our software; it applies also to |
|||
any other work released this way by its authors. You can apply it to |
|||
your programs, too. |
|||
|
|||
When we speak of free software, we are referring to freedom, not |
|||
price. Our General Public Licenses are designed to make sure that you |
|||
have the freedom to distribute copies of free software (and charge for |
|||
them if you wish), that you receive source code or can get it if you |
|||
want it, that you can change the software or use pieces of it in new |
|||
free programs, and that you know you can do these things. |
|||
|
|||
To protect your rights, we need to prevent others from denying you |
|||
these rights or asking you to surrender the rights. Therefore, you have |
|||
certain responsibilities if you distribute copies of the software, or if |
|||
you modify it: responsibilities to respect the freedom of others. |
|||
|
|||
For example, if you distribute copies of such a program, whether |
|||
gratis or for a fee, you must pass on to the recipients the same |
|||
freedoms that you received. You must make sure that they, too, receive |
|||
or can get the source code. And you must show them these terms so they |
|||
know their rights. |
|||
|
|||
Developers that use the GNU GPL protect your rights with two steps: |
|||
(1) assert copyright on the software, and (2) offer you this License |
|||
giving you legal permission to copy, distribute and/or modify it. |
|||
|
|||
For the developers' and authors' protection, the GPL clearly explains |
|||
that there is no warranty for this free software. For both users' and |
|||
authors' sake, the GPL requires that modified versions be marked as |
|||
changed, so that their problems will not be attributed erroneously to |
|||
authors of previous versions. |
|||
|
|||
Some devices are designed to deny users access to install or run |
|||
modified versions of the software inside them, although the manufacturer |
|||
can do so. This is fundamentally incompatible with the aim of |
|||
protecting users' freedom to change the software. The systematic |
|||
pattern of such abuse occurs in the area of products for individuals to |
|||
use, which is precisely where it is most unacceptable. Therefore, we |
|||
have designed this version of the GPL to prohibit the practice for those |
|||
products. If such problems arise substantially in other domains, we |
|||
stand ready to extend this provision to those domains in future versions |
|||
of the GPL, as needed to protect the freedom of users. |
|||
|
|||
Finally, every program is threatened constantly by software patents. |
|||
States should not allow patents to restrict development and use of |
|||
software on general-purpose computers, but in those that do, we wish to |
|||
avoid the special danger that patents applied to a free program could |
|||
make it effectively proprietary. To prevent this, the GPL assures that |
|||
patents cannot be used to render the program non-free. |
|||
|
|||
The precise terms and conditions for copying, distribution and |
|||
modification follow. |
|||
|
|||
TERMS AND CONDITIONS |
|||
|
|||
0. Definitions. |
|||
|
|||
"This License" refers to version 3 of the GNU General Public License. |
|||
|
|||
"Copyright" also means copyright-like laws that apply to other kinds of |
|||
works, such as semiconductor masks. |
|||
|
|||
"The Program" refers to any copyrightable work licensed under this |
|||
License. Each licensee is addressed as "you". "Licensees" and |
|||
"recipients" may be individuals or organizations. |
|||
|
|||
To "modify" a work means to copy from or adapt all or part of the work |
|||
in a fashion requiring copyright permission, other than the making of an |
|||
exact copy. The resulting work is called a "modified version" of the |
|||
earlier work or a work "based on" the earlier work. |
|||
|
|||
A "covered work" means either the unmodified Program or a work based |
|||
on the Program. |
|||
|
|||
To "propagate" a work means to do anything with it that, without |
|||
permission, would make you directly or secondarily liable for |
|||
infringement under applicable copyright law, except executing it on a |
|||
computer or modifying a private copy. Propagation includes copying, |
|||
distribution (with or without modification), making available to the |
|||
public, and in some countries other activities as well. |
|||
|
|||
To "convey" a work means any kind of propagation that enables other |
|||
parties to make or receive copies. Mere interaction with a user through |
|||
a computer network, with no transfer of a copy, is not conveying. |
|||
|
|||
An interactive user interface displays "Appropriate Legal Notices" |
|||
to the extent that it includes a convenient and prominently visible |
|||
feature that (1) displays an appropriate copyright notice, and (2) |
|||
tells the user that there is no warranty for the work (except to the |
|||
extent that warranties are provided), that licensees may convey the |
|||
work under this License, and how to view a copy of this License. If |
|||
the interface presents a list of user commands or options, such as a |
|||
menu, a prominent item in the list meets this criterion. |
|||
|
|||
1. Source Code. |
|||
|
|||
The "source code" for a work means the preferred form of the work |
|||
for making modifications to it. "Object code" means any non-source |
|||
form of a work. |
|||
|
|||
A "Standard Interface" means an interface that either is an official |
|||
standard defined by a recognized standards body, or, in the case of |
|||
interfaces specified for a particular programming language, one that |
|||
is widely used among developers working in that language. |
|||
|
|||
The "System Libraries" of an executable work include anything, other |
|||
than the work as a whole, that (a) is included in the normal form of |
|||
packaging a Major Component, but which is not part of that Major |
|||
Component, and (b) serves only to enable use of the work with that |
|||
Major Component, or to implement a Standard Interface for which an |
|||
implementation is available to the public in source code form. A |
|||
"Major Component", in this context, means a major essential component |
|||
(kernel, window system, and so on) of the specific operating system |
|||
(if any) on which the executable work runs, or a compiler used to |
|||
produce the work, or an object code interpreter used to run it. |
|||
|
|||
The "Corresponding Source" for a work in object code form means all |
|||
the source code needed to generate, install, and (for an executable |
|||
work) run the object code and to modify the work, including scripts to |
|||
control those activities. However, it does not include the work's |
|||
System Libraries, or general-purpose tools or generally available free |
|||
programs which are used unmodified in performing those activities but |
|||
which are not part of the work. For example, Corresponding Source |
|||
includes interface definition files associated with source files for |
|||
the work, and the source code for shared libraries and dynamically |
|||
linked subprograms that the work is specifically designed to require, |
|||
such as by intimate data communication or control flow between those |
|||
subprograms and other parts of the work. |
|||
|
|||
The Corresponding Source need not include anything that users |
|||
can regenerate automatically from other parts of the Corresponding |
|||
Source. |
|||
|
|||
The Corresponding Source for a work in source code form is that |
|||
same work. |
|||
|
|||
2. Basic Permissions. |
|||
|
|||
All rights granted under this License are granted for the term of |
|||
copyright on the Program, and are irrevocable provided the stated |
|||
conditions are met. This License explicitly affirms your unlimited |
|||
permission to run the unmodified Program. The output from running a |
|||
covered work is covered by this License only if the output, given its |
|||
content, constitutes a covered work. This License acknowledges your |
|||
rights of fair use or other equivalent, as provided by copyright law. |
|||
|
|||
You may make, run and propagate covered works that you do not |
|||
convey, without conditions so long as your license otherwise remains |
|||
in force. You may convey covered works to others for the sole purpose |
|||
of having them make modifications exclusively for you, or provide you |
|||
with facilities for running those works, provided that you comply with |
|||
the terms of this License in conveying all material for which you do |
|||
not control copyright. Those thus making or running the covered works |
|||
for you must do so exclusively on your behalf, under your direction |
|||
and control, on terms that prohibit them from making any copies of |
|||
your copyrighted material outside their relationship with you. |
|||
|
|||
Conveying under any other circumstances is permitted solely under |
|||
the conditions stated below. Sublicensing is not allowed; section 10 |
|||
makes it unnecessary. |
|||
|
|||
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
|||
|
|||
No covered work shall be deemed part of an effective technological |
|||
measure under any applicable law fulfilling obligations under article |
|||
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
|||
similar laws prohibiting or restricting circumvention of such |
|||
measures. |
|||
|
|||
When you convey a covered work, you waive any legal power to forbid |
|||
circumvention of technological measures to the extent such circumvention |
|||
is effected by exercising rights under this License with respect to |
|||
the covered work, and you disclaim any intention to limit operation or |
|||
modification of the work as a means of enforcing, against the work's |
|||
users, your or third parties' legal rights to forbid circumvention of |
|||
technological measures. |
|||
|
|||
4. Conveying Verbatim Copies. |
|||
|
|||
You may convey verbatim copies of the Program's source code as you |
|||
receive it, in any medium, provided that you conspicuously and |
|||
appropriately publish on each copy an appropriate copyright notice; |
|||
keep intact all notices stating that this License and any |
|||
non-permissive terms added in accord with section 7 apply to the code; |
|||
keep intact all notices of the absence of any warranty; and give all |
|||
recipients a copy of this License along with the Program. |
|||
|
|||
You may charge any price or no price for each copy that you convey, |
|||
and you may offer support or warranty protection for a fee. |
|||
|
|||
5. Conveying Modified Source Versions. |
|||
|
|||
You may convey a work based on the Program, or the modifications to |
|||
produce it from the Program, in the form of source code under the |
|||
terms of section 4, provided that you also meet all of these conditions: |
|||
|
|||
a) The work must carry prominent notices stating that you modified |
|||
it, and giving a relevant date. |
|||
|
|||
b) The work must carry prominent notices stating that it is |
|||
released under this License and any conditions added under section |
|||
7. This requirement modifies the requirement in section 4 to |
|||
"keep intact all notices". |
|||
|
|||
c) You must license the entire work, as a whole, under this |
|||
License to anyone who comes into possession of a copy. This |
|||
License will therefore apply, along with any applicable section 7 |
|||
additional terms, to the whole of the work, and all its parts, |
|||
regardless of how they are packaged. This License gives no |
|||
permission to license the work in any other way, but it does not |
|||
invalidate such permission if you have separately received it. |
|||
|
|||
d) If the work has interactive user interfaces, each must display |
|||
Appropriate Legal Notices; however, if the Program has interactive |
|||
interfaces that do not display Appropriate Legal Notices, your |
|||
work need not make them do so. |
|||
|
|||
A compilation of a covered work with other separate and independent |
|||
works, which are not by their nature extensions of the covered work, |
|||
and which are not combined with it such as to form a larger program, |
|||
in or on a volume of a storage or distribution medium, is called an |
|||
"aggregate" if the compilation and its resulting copyright are not |
|||
used to limit the access or legal rights of the compilation's users |
|||
beyond what the individual works permit. Inclusion of a covered work |
|||
in an aggregate does not cause this License to apply to the other |
|||
parts of the aggregate. |
|||
|
|||
6. Conveying Non-Source Forms. |
|||
|
|||
You may convey a covered work in object code form under the terms |
|||
of sections 4 and 5, provided that you also convey the |
|||
machine-readable Corresponding Source under the terms of this License, |
|||
in one of these ways: |
|||
|
|||
a) Convey the object code in, or embodied in, a physical product |
|||
(including a physical distribution medium), accompanied by the |
|||
Corresponding Source fixed on a durable physical medium |
|||
customarily used for software interchange. |
|||
|
|||
b) Convey the object code in, or embodied in, a physical product |
|||
(including a physical distribution medium), accompanied by a |
|||
written offer, valid for at least three years and valid for as |
|||
long as you offer spare parts or customer support for that product |
|||
model, to give anyone who possesses the object code either (1) a |
|||
copy of the Corresponding Source for all the software in the |
|||
product that is covered by this License, on a durable physical |
|||
medium customarily used for software interchange, for a price no |
|||
more than your reasonable cost of physically performing this |
|||
conveying of source, or (2) access to copy the |
|||
Corresponding Source from a network server at no charge. |
|||
|
|||
c) Convey individual copies of the object code with a copy of the |
|||
written offer to provide the Corresponding Source. This |
|||
alternative is allowed only occasionally and noncommercially, and |
|||
only if you received the object code with such an offer, in accord |
|||
with subsection 6b. |
|||
|
|||
d) Convey the object code by offering access from a designated |
|||
place (gratis or for a charge), and offer equivalent access to the |
|||
Corresponding Source in the same way through the same place at no |
|||
further charge. You need not require recipients to copy the |
|||
Corresponding Source along with the object code. If the place to |
|||
copy the object code is a network server, the Corresponding Source |
|||
may be on a different server (operated by you or a third party) |
|||
that supports equivalent copying facilities, provided you maintain |
|||
clear directions next to the object code saying where to find the |
|||
Corresponding Source. Regardless of what server hosts the |
|||
Corresponding Source, you remain obligated to ensure that it is |
|||
available for as long as needed to satisfy these requirements. |
|||
|
|||
e) Convey the object code using peer-to-peer transmission, provided |
|||
you inform other peers where the object code and Corresponding |
|||
Source of the work are being offered to the general public at no |
|||
charge under subsection 6d. |
|||
|
|||
A separable portion of the object code, whose source code is excluded |
|||
from the Corresponding Source as a System Library, need not be |
|||
included in conveying the object code work. |
|||
|
|||
A "User Product" is either (1) a "consumer product", which means any |
|||
tangible personal property which is normally used for personal, family, |
|||
or household purposes, or (2) anything designed or sold for incorporation |
|||
into a dwelling. In determining whether a product is a consumer product, |
|||
doubtful cases shall be resolved in favor of coverage. For a particular |
|||
product received by a particular user, "normally used" refers to a |
|||
typical or common use of that class of product, regardless of the status |
|||
of the particular user or of the way in which the particular user |
|||
actually uses, or expects or is expected to use, the product. A product |
|||
is a consumer product regardless of whether the product has substantial |
|||
commercial, industrial or non-consumer uses, unless such uses represent |
|||
the only significant mode of use of the product. |
|||
|
|||
"Installation Information" for a User Product means any methods, |
|||
procedures, authorization keys, or other information required to install |
|||
and execute modified versions of a covered work in that User Product from |
|||
a modified version of its Corresponding Source. The information must |
|||
suffice to ensure that the continued functioning of the modified object |
|||
code is in no case prevented or interfered with solely because |
|||
modification has been made. |
|||
|
|||
If you convey an object code work under this section in, or with, or |
|||
specifically for use in, a User Product, and the conveying occurs as |
|||
part of a transaction in which the right of possession and use of the |
|||
User Product is transferred to the recipient in perpetuity or for a |
|||
fixed term (regardless of how the transaction is characterized), the |
|||
Corresponding Source conveyed under this section must be accompanied |
|||
by the Installation Information. But this requirement does not apply |
|||
if neither you nor any third party retains the ability to install |
|||
modified object code on the User Product (for example, the work has |
|||
been installed in ROM). |
|||
|
|||
The requirement to provide Installation Information does not include a |
|||
requirement to continue to provide support service, warranty, or updates |
|||
for a work that has been modified or installed by the recipient, or for |
|||
the User Product in which it has been modified or installed. Access to a |
|||
network may be denied when the modification itself materially and |
|||
adversely affects the operation of the network or violates the rules and |
|||
protocols for communication across the network. |
|||
|
|||
Corresponding Source conveyed, and Installation Information provided, |
|||
in accord with this section must be in a format that is publicly |
|||
documented (and with an implementation available to the public in |
|||
source code form), and must require no special password or key for |
|||
unpacking, reading or copying. |
|||
|
|||
7. Additional Terms. |
|||
|
|||
"Additional permissions" are terms that supplement the terms of this |
|||
License by making exceptions from one or more of its conditions. |
|||
Additional permissions that are applicable to the entire Program shall |
|||
be treated as though they were included in this License, to the extent |
|||
that they are valid under applicable law. If additional permissions |
|||
apply only to part of the Program, that part may be used separately |
|||
under those permissions, but the entire Program remains governed by |
|||
this License without regard to the additional permissions. |
|||
|
|||
When you convey a copy of a covered work, you may at your option |
|||
remove any additional permissions from that copy, or from any part of |
|||
it. (Additional permissions may be written to require their own |
|||
removal in certain cases when you modify the work.) You may place |
|||
additional permissions on material, added by you to a covered work, |
|||
for which you have or can give appropriate copyright permission. |
|||
|
|||
Notwithstanding any other provision of this License, for material you |
|||
add to a covered work, you may (if authorized by the copyright holders of |
|||
that material) supplement the terms of this License with terms: |
|||
|
|||
a) Disclaiming warranty or limiting liability differently from the |
|||
terms of sections 15 and 16 of this License; or |
|||
|
|||
b) Requiring preservation of specified reasonable legal notices or |
|||
author attributions in that material or in the Appropriate Legal |
|||
Notices displayed by works containing it; or |
|||
|
|||
c) Prohibiting misrepresentation of the origin of that material, or |
|||
requiring that modified versions of such material be marked in |
|||
reasonable ways as different from the original version; or |
|||
|
|||
d) Limiting the use for publicity purposes of names of licensors or |
|||
authors of the material; or |
|||
|
|||
e) Declining to grant rights under trademark law for use of some |
|||
trade names, trademarks, or service marks; or |
|||
|
|||
f) Requiring indemnification of licensors and authors of that |
|||
material by anyone who conveys the material (or modified versions of |
|||
it) with contractual assumptions of liability to the recipient, for |
|||
any liability that these contractual assumptions directly impose on |
|||
those licensors and authors. |
|||
|
|||
All other non-permissive additional terms are considered "further |
|||
restrictions" within the meaning of section 10. If the Program as you |
|||
received it, or any part of it, contains a notice stating that it is |
|||
governed by this License along with a term that is a further |
|||
restriction, you may remove that term. If a license document contains |
|||
a further restriction but permits relicensing or conveying under this |
|||
License, you may add to a covered work material governed by the terms |
|||
of that license document, provided that the further restriction does |
|||
not survive such relicensing or conveying. |
|||
|
|||
If you add terms to a covered work in accord with this section, you |
|||
must place, in the relevant source files, a statement of the |
|||
additional terms that apply to those files, or a notice indicating |
|||
where to find the applicable terms. |
|||
|
|||
Additional terms, permissive or non-permissive, may be stated in the |
|||
form of a separately written license, or stated as exceptions; |
|||
the above requirements apply either way. |
|||
|
|||
8. Termination. |
|||
|
|||
You may not propagate or modify a covered work except as expressly |
|||
provided under this License. Any attempt otherwise to propagate or |
|||
modify it is void, and will automatically terminate your rights under |
|||
this License (including any patent licenses granted under the third |
|||
paragraph of section 11). |
|||
|
|||
However, if you cease all violation of this License, then your |
|||
license from a particular copyright holder is reinstated (a) |
|||
provisionally, unless and until the copyright holder explicitly and |
|||
finally terminates your license, and (b) permanently, if the copyright |
|||
holder fails to notify you of the violation by some reasonable means |
|||
prior to 60 days after the cessation. |
|||
|
|||
Moreover, your license from a particular copyright holder is |
|||
reinstated permanently if the copyright holder notifies you of the |
|||
violation by some reasonable means, this is the first time you have |
|||
received notice of violation of this License (for any work) from that |
|||
copyright holder, and you cure the violation prior to 30 days after |
|||
your receipt of the notice. |
|||
|
|||
Termination of your rights under this section does not terminate the |
|||
licenses of parties who have received copies or rights from you under |
|||
this License. If your rights have been terminated and not permanently |
|||
reinstated, you do not qualify to receive new licenses for the same |
|||
material under section 10. |
|||
|
|||
9. Acceptance Not Required for Having Copies. |
|||
|
|||
You are not required to accept this License in order to receive or |
|||
run a copy of the Program. Ancillary propagation of a covered work |
|||
occurring solely as a consequence of using peer-to-peer transmission |
|||
to receive a copy likewise does not require acceptance. However, |
|||
nothing other than this License grants you permission to propagate or |
|||
modify any covered work. These actions infringe copyright if you do |
|||
not accept this License. Therefore, by modifying or propagating a |
|||
covered work, you indicate your acceptance of this License to do so. |
|||
|
|||
10. Automatic Licensing of Downstream Recipients. |
|||
|
|||
Each time you convey a covered work, the recipient automatically |
|||
receives a license from the original licensors, to run, modify and |
|||
propagate that work, subject to this License. You are not responsible |
|||
for enforcing compliance by third parties with this License. |
|||
|
|||
An "entity transaction" is a transaction transferring control of an |
|||
organization, or substantially all assets of one, or subdividing an |
|||
organization, or merging organizations. If propagation of a covered |
|||
work results from an entity transaction, each party to that |
|||
transaction who receives a copy of the work also receives whatever |
|||
licenses to the work the party's predecessor in interest had or could |
|||
give under the previous paragraph, plus a right to possession of the |
|||
Corresponding Source of the work from the predecessor in interest, if |
|||
the predecessor has it or can get it with reasonable efforts. |
|||
|
|||
You may not impose any further restrictions on the exercise of the |
|||
rights granted or affirmed under this License. For example, you may |
|||
not impose a license fee, royalty, or other charge for exercise of |
|||
rights granted under this License, and you may not initiate litigation |
|||
(including a cross-claim or counterclaim in a lawsuit) alleging that |
|||
any patent claim is infringed by making, using, selling, offering for |
|||
sale, or importing the Program or any portion of it. |
|||
|
|||
11. Patents. |
|||
|
|||
A "contributor" is a copyright holder who authorizes use under this |
|||
License of the Program or a work on which the Program is based. The |
|||
work thus licensed is called the contributor's "contributor version". |
|||
|
|||
A contributor's "essential patent claims" are all patent claims |
|||
owned or controlled by the contributor, whether already acquired or |
|||
hereafter acquired, that would be infringed by some manner, permitted |
|||
by this License, of making, using, or selling its contributor version, |
|||
but do not include claims that would be infringed only as a |
|||
consequence of further modification of the contributor version. For |
|||
purposes of this definition, "control" includes the right to grant |
|||
patent sublicenses in a manner consistent with the requirements of |
|||
this License. |
|||
|
|||
Each contributor grants you a non-exclusive, worldwide, royalty-free |
|||
patent license under the contributor's essential patent claims, to |
|||
make, use, sell, offer for sale, import and otherwise run, modify and |
|||
propagate the contents of its contributor version. |
|||
|
|||
In the following three paragraphs, a "patent license" is any express |
|||
agreement or commitment, however denominated, not to enforce a patent |
|||
(such as an express permission to practice a patent or covenant not to |
|||
sue for patent infringement). To "grant" such a patent license to a |
|||
party means to make such an agreement or commitment not to enforce a |
|||
patent against the party. |
|||
|
|||
If you convey a covered work, knowingly relying on a patent license, |
|||
and the Corresponding Source of the work is not available for anyone |
|||
to copy, free of charge and under the terms of this License, through a |
|||
publicly available network server or other readily accessible means, |
|||
then you must either (1) cause the Corresponding Source to be so |
|||
available, or (2) arrange to deprive yourself of the benefit of the |
|||
patent license for this particular work, or (3) arrange, in a manner |
|||
consistent with the requirements of this License, to extend the patent |
|||
license to downstream recipients. "Knowingly relying" means you have |
|||
actual knowledge that, but for the patent license, your conveying the |
|||
covered work in a country, or your recipient's use of the covered work |
|||
in a country, would infringe one or more identifiable patents in that |
|||
country that you have reason to believe are valid. |
|||
|
|||
If, pursuant to or in connection with a single transaction or |
|||
arrangement, you convey, or propagate by procuring conveyance of, a |
|||
covered work, and grant a patent license to some of the parties |
|||
receiving the covered work authorizing them to use, propagate, modify |
|||
or convey a specific copy of the covered work, then the patent license |
|||
you grant is automatically extended to all recipients of the covered |
|||
work and works based on it. |
|||
|
|||
A patent license is "discriminatory" if it does not include within |
|||
the scope of its coverage, prohibits the exercise of, or is |
|||
conditioned on the non-exercise of one or more of the rights that are |
|||
specifically granted under this License. You may not convey a covered |
|||
work if you are a party to an arrangement with a third party that is |
|||
in the business of distributing software, under which you make payment |
|||
to the third party based on the extent of your activity of conveying |
|||
the work, and under which the third party grants, to any of the |
|||
parties who would receive the covered work from you, a discriminatory |
|||
patent license (a) in connection with copies of the covered work |
|||
conveyed by you (or copies made from those copies), or (b) primarily |
|||
for and in connection with specific products or compilations that |
|||
contain the covered work, unless you entered into that arrangement, |
|||
or that patent license was granted, prior to 28 March 2007. |
|||
|
|||
Nothing in this License shall be construed as excluding or limiting |
|||
any implied license or other defenses to infringement that may |
|||
otherwise be available to you under applicable patent law. |
|||
|
|||
12. No Surrender of Others' Freedom. |
|||
|
|||
If conditions are imposed on you (whether by court order, agreement or |
|||
otherwise) that contradict the conditions of this License, they do not |
|||
excuse you from the conditions of this License. If you cannot convey a |
|||
covered work so as to satisfy simultaneously your obligations under this |
|||
License and any other pertinent obligations, then as a consequence you may |
|||
not convey it at all. For example, if you agree to terms that obligate you |
|||
to collect a royalty for further conveying from those to whom you convey |
|||
the Program, the only way you could satisfy both those terms and this |
|||
License would be to refrain entirely from conveying the Program. |
|||
|
|||
13. Use with the GNU Affero General Public License. |
|||
|
|||
Notwithstanding any other provision of this License, you have |
|||
permission to link or combine any covered work with a work licensed |
|||
under version 3 of the GNU Affero General Public License into a single |
|||
combined work, and to convey the resulting work. The terms of this |
|||
License will continue to apply to the part which is the covered work, |
|||
but the special requirements of the GNU Affero General Public License, |
|||
section 13, concerning interaction through a network will apply to the |
|||
combination as such. |
|||
|
|||
14. Revised Versions of this License. |
|||
|
|||
The Free Software Foundation may publish revised and/or new versions of |
|||
the GNU General Public License from time to time. Such new versions will |
|||
be similar in spirit to the present version, but may differ in detail to |
|||
address new problems or concerns. |
|||
|
|||
Each version is given a distinguishing version number. If the |
|||
Program specifies that a certain numbered version of the GNU General |
|||
Public License "or any later version" applies to it, you have the |
|||
option of following the terms and conditions either of that numbered |
|||
version or of any later version published by the Free Software |
|||
Foundation. If the Program does not specify a version number of the |
|||
GNU General Public License, you may choose any version ever published |
|||
by the Free Software Foundation. |
|||
|
|||
If the Program specifies that a proxy can decide which future |
|||
versions of the GNU General Public License can be used, that proxy's |
|||
public statement of acceptance of a version permanently authorizes you |
|||
to choose that version for the Program. |
|||
|
|||
Later license versions may give you additional or different |
|||
permissions. However, no additional obligations are imposed on any |
|||
author or copyright holder as a result of your choosing to follow a |
|||
later version. |
|||
|
|||
15. Disclaimer of Warranty. |
|||
|
|||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
|||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
|||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
|||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
|||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
|||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
|||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
|||
|
|||
16. Limitation of Liability. |
|||
|
|||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
|||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
|||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
|||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
|||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
|||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
|||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
|||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
|||
SUCH DAMAGES. |
|||
|
|||
17. Interpretation of Sections 15 and 16. |
|||
|
|||
If the disclaimer of warranty and limitation of liability provided |
|||
above cannot be given local legal effect according to their terms, |
|||
reviewing courts shall apply local law that most closely approximates |
|||
an absolute waiver of all civil liability in connection with the |
|||
Program, unless a warranty or assumption of liability accompanies a |
|||
copy of the Program in return for a fee. |
|||
|
|||
END OF TERMS AND CONDITIONS |
|||
|
|||
How to Apply These Terms to Your New Programs |
|||
|
|||
If you develop a new program, and you want it to be of the greatest |
|||
possible use to the public, the best way to achieve this is to make it |
|||
free software which everyone can redistribute and change under these terms. |
|||
|
|||
To do so, attach the following notices to the program. It is safest |
|||
to attach them to the start of each source file to most effectively |
|||
state the exclusion of warranty; and each file should have at least |
|||
the "copyright" line and a pointer to where the full notice is found. |
|||
|
|||
<one line to give the program's name and a brief idea of what it does.> |
|||
Copyright (C) <year> <name of author> |
|||
|
|||
This program is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
This program is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
Also add information on how to contact you by electronic and paper mail. |
|||
|
|||
If the program does terminal interaction, make it output a short |
|||
notice like this when it starts in an interactive mode: |
|||
|
|||
<program> Copyright (C) <year> <name of author> |
|||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
|||
This is free software, and you are welcome to redistribute it |
|||
under certain conditions; type `show c' for details. |
|||
|
|||
The hypothetical commands `show w' and `show c' should show the appropriate |
|||
parts of the General Public License. Of course, your program's commands |
|||
might be different; for a GUI interface, you would use an "about box". |
|||
|
|||
You should also get your employer (if you work as a programmer) or school, |
|||
if any, to sign a "copyright disclaimer" for the program, if necessary. |
|||
For more information on this, and how to apply and follow the GNU GPL, see |
|||
<http://www.gnu.org/licenses/>. |
|||
|
|||
The GNU General Public License does not permit incorporating your program |
|||
into proprietary programs. If your program is a subroutine library, you |
|||
may consider it more useful to permit linking proprietary applications with |
|||
the library. If this is what you want to do, use the GNU Lesser General |
|||
Public License instead of this License. But first, please read |
|||
<http://www.gnu.org/philosophy/why-not-lgpl.html>. |
@ -0,0 +1,167 @@ |
|||
# Spring Boot Shopping Cart Web App |
|||
|
|||
## About |
|||
|
|||
This is a demo project for practicing Spring + Thymeleaf. The idea was to build some basic shopping cart web app. |
|||
|
|||
It was made using **Spring Boot**, **Spring Security**, **Thymeleaf**, **Spring Data JPA**, **Spring Data REST and Docker**. |
|||
Database is in memory **H2**. |
|||
|
|||
There is a login and registration functionality included. |
|||
|
|||
Users can shop for products. Each user has his own shopping cart (session functionality). |
|||
Checkout is transactional. |
|||
|
|||
## Configuration |
|||
|
|||
### Configuration Files |
|||
|
|||
Folder **src/resources/** contains config files for **shopping-cart** Spring Boot application. |
|||
|
|||
* **src/resources/application.properties** - main configuration file. Here it is possible to change admin username/password, |
|||
as well as change the port number. |
|||
|
|||
## How to run |
|||
|
|||
There are several ways to run the application. You can run it from the command line with included Maven Wrapper, Maven or Docker. |
|||
|
|||
Once the app starts, go to the web browser and visit `http://localhost:8070/home` |
|||
|
|||
Admin username: **admin** |
|||
|
|||
Admin password: **admin** |
|||
|
|||
User username: **user** |
|||
|
|||
User password: **password** |
|||
|
|||
### Maven Wrapper |
|||
|
|||
#### Using the Maven Plugin |
|||
|
|||
Go to the root folder of the application and type: |
|||
```bash |
|||
$ chmod +x scripts/mvnw |
|||
$ scripts/mvnw spring-boot:run |
|||
``` |
|||
|
|||
#### Using Executable Jar |
|||
|
|||
Or you can build the JAR file with |
|||
```bash |
|||
$ scripts/mvnw clean package |
|||
``` |
|||
|
|||
Then you can run the JAR file: |
|||
```bash |
|||
$ java -jar target/shopping-cart-0.0.1-SNAPSHOT.jar |
|||
``` |
|||
|
|||
### Maven |
|||
|
|||
Open a terminal and run the following commands to ensure that you have valid versions of Java and Maven installed: |
|||
|
|||
```bash |
|||
$ java -version |
|||
java version "1.8.0_102" |
|||
Java(TM) SE Runtime Environment (build 1.8.0_102-b14) |
|||
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode) |
|||
``` |
|||
|
|||
```bash |
|||
$ mvn -v |
|||
Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-10T16:41:47+00:00) |
|||
Maven home: /usr/local/Cellar/maven/3.3.9/libexec |
|||
Java version: 1.8.0_102, vendor: Oracle Corporation |
|||
``` |
|||
|
|||
#### Using the Maven Plugin |
|||
|
|||
The Spring Boot Maven plugin includes a run goal that can be used to quickly compile and run your application. |
|||
Applications run in an exploded form, as they do in your IDE. |
|||
The following example shows a typical Maven command to run a Spring Boot application: |
|||
|
|||
```bash |
|||
$ mvn spring-boot:run |
|||
``` |
|||
|
|||
#### Using Executable Jar |
|||
|
|||
To create an executable jar run: |
|||
|
|||
```bash |
|||
$ mvn clean package |
|||
``` |
|||
|
|||
To run that application, use the java -jar command, as follows: |
|||
|
|||
```bash |
|||
$ java -jar target/shopping-cart-0.0.1-SNAPSHOT.jar |
|||
``` |
|||
|
|||
To exit the application, press **ctrl-c**. |
|||
|
|||
### Docker |
|||
|
|||
It is possible to run **shopping-cart** using Docker: |
|||
|
|||
Build Docker image: |
|||
```bash |
|||
$ mvn clean package |
|||
$ docker build -t shopping-cart:dev -f docker/Dockerfile . |
|||
``` |
|||
|
|||
Run Docker container: |
|||
```bash |
|||
$ docker run --rm -i -p 8070:8070 \ |
|||
--name shopping-cart \ |
|||
shopping-cart:dev |
|||
``` |
|||
|
|||
##### Helper script |
|||
|
|||
It is possible to run all of the above with helper script: |
|||
|
|||
```bash |
|||
$ chmod +x scripts/run_docker.sh |
|||
$ scripts/run_docker.sh |
|||
``` |
|||
|
|||
## Docker |
|||
|
|||
Folder **docker** contains: |
|||
|
|||
* **docker/shopping-cart/Dockerfile** - Docker build file for executing shopping-cart Docker image. |
|||
Instructions to build artifacts, copy build artifacts to docker image and then run app on proper port with proper configuration file. |
|||
|
|||
## Util Scripts |
|||
|
|||
* **scripts/run_docker.sh.sh** - util script for running shopping-cart Docker container using **docker/Dockerfile** |
|||
|
|||
## Tests |
|||
|
|||
Tests can be run by executing following command from the root of the project: |
|||
|
|||
```bash |
|||
$ mvn test |
|||
``` |
|||
|
|||
## Helper Tools |
|||
|
|||
### HAL REST Browser |
|||
|
|||
Go to the web browser and visit `http://localhost:8070/` |
|||
|
|||
You will need to be authenticated to be able to see this page. |
|||
|
|||
### H2 Database web interface |
|||
|
|||
Go to the web browser and visit `http://localhost:8070/h2-console` |
|||
|
|||
In field **JDBC URL** put |
|||
``` |
|||
jdbc:h2:mem:shopping_cart_db |
|||
``` |
|||
|
|||
In `/src/main/resources/application.properties` file it is possible to change both |
|||
web interface url path, as well as the datasource url. |
@ -0,0 +1,38 @@ |
|||
apiVersion: apps/v1 |
|||
kind: Deployment # Kubernetes resource kind we are creating |
|||
metadata: |
|||
name: ekart-deployment |
|||
spec: |
|||
selector: |
|||
matchLabels: |
|||
app: ekart |
|||
replicas: 2 # Number of replicas that will be created for this deployment |
|||
template: |
|||
metadata: |
|||
labels: |
|||
app: ekart |
|||
spec: |
|||
containers: |
|||
- name: ekart |
|||
image: adijaiswal/ekart:latest # Image that will be used to containers in the cluster |
|||
imagePullPolicy: IfNotPresent |
|||
ports: |
|||
- containerPort: 8070 # The port that the container is running on in the cluster |
|||
|
|||
|
|||
--- |
|||
|
|||
apiVersion: v1 # Kubernetes API version |
|||
kind: Service # Kubernetes resource kind we are creating |
|||
metadata: # Metadata of the resource kind we are creating |
|||
name: ekart-ssvc |
|||
spec: |
|||
selector: |
|||
app: ekart |
|||
ports: |
|||
- protocol: "TCP" |
|||
port: 8070 # The port that the service is running on in the cluster |
|||
targetPort: 8070 # The port exposed by the service |
|||
type: LoadBalancer # type of the service. |
|||
|
|||
|
@ -0,0 +1,11 @@ |
|||
FROM openjdk:8u151-jdk-alpine3.7 |
|||
|
|||
EXPOSE 8070 |
|||
|
|||
ENV APP_HOME /usr/src/app |
|||
|
|||
COPY target/shopping-cart-0.0.1-SNAPSHOT.jar $APP_HOME/app.jar |
|||
|
|||
WORKDIR $APP_HOME |
|||
|
|||
ENTRYPOINT exec java -jar app.jar |
@ -0,0 +1,165 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
|
|||
<groupId>com.reljicd</groupId> |
|||
<artifactId>shopping-cart</artifactId> |
|||
<version>0.0.1-SNAPSHOT</version> |
|||
<packaging>jar</packaging> |
|||
|
|||
<name>shopping-cart</name> |
|||
<description>Demo project for Spring Boot Shopping Cart</description> |
|||
|
|||
<parent> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-parent</artifactId> |
|||
<version>1.5.3.RELEASE</version> |
|||
<relativePath/> <!-- lookup parent from repository --> |
|||
</parent> |
|||
|
|||
<properties> |
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> |
|||
<java.version>1.8</java.version> |
|||
<jacoco.version>0.8.6</jacoco.version> |
|||
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin> |
|||
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis> |
|||
<sonar.jacoco.reportPath>${project.basedir}/../target/jacoco.exec</sonar.jacoco.reportPath> |
|||
<sonar.language>java</sonar.language> |
|||
</properties> |
|||
|
|||
<dependencies> |
|||
<!-- Data JPA --> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-data-jpa</artifactId> |
|||
</dependency> |
|||
|
|||
<!-- Data REST --> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-data-rest</artifactId> |
|||
</dependency> |
|||
|
|||
<!--HAL REST Browser --> |
|||
<dependency> |
|||
<groupId>org.springframework.data</groupId> |
|||
<artifactId>spring-data-rest-hal-browser</artifactId> |
|||
</dependency> |
|||
|
|||
<!-- Spring Security --> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-security</artifactId> |
|||
</dependency> |
|||
|
|||
<!-- Thymeleaf --> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-thymeleaf</artifactId> |
|||
</dependency> |
|||
|
|||
<dependency> |
|||
<groupId>org.thymeleaf.extras</groupId> |
|||
<artifactId>thymeleaf-extras-springsecurity4</artifactId> |
|||
</dependency> |
|||
|
|||
<!-- Spring MVC --> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-web</artifactId> |
|||
</dependency> |
|||
|
|||
<!-- hot swapping, disable cache for template, enable live reload --> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-devtools</artifactId> |
|||
<scope>runtime</scope> |
|||
</dependency> |
|||
|
|||
<!-- H2 --> |
|||
<dependency> |
|||
<groupId>com.h2database</groupId> |
|||
<artifactId>h2</artifactId> |
|||
<scope>runtime</scope> |
|||
</dependency> |
|||
|
|||
<!-- MySQL --> |
|||
<dependency> |
|||
<groupId>mysql</groupId> |
|||
<artifactId>mysql-connector-java</artifactId> |
|||
<scope>runtime</scope> |
|||
</dependency> |
|||
|
|||
<!-- Tests --> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-test</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
|
|||
<!-- Optional, for bootstrap --> |
|||
<dependency> |
|||
<groupId>org.webjars</groupId> |
|||
<artifactId>bootstrap</artifactId> |
|||
<version>3.3.7</version> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.jacoco</groupId> |
|||
<artifactId>jacoco-maven-plugin</artifactId> |
|||
<version>0.8.6</version> |
|||
</dependency> |
|||
<!-- Optional, for jQuery --> |
|||
<dependency> |
|||
<groupId>org.webjars</groupId> |
|||
<artifactId>jquery</artifactId> |
|||
<version>2.1.4</version> |
|||
</dependency> |
|||
</dependencies> |
|||
|
|||
<build> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-maven-plugin</artifactId> |
|||
</plugin> |
|||
|
|||
<plugin> |
|||
<groupId>org.jacoco</groupId> |
|||
<artifactId>jacoco-maven-plugin</artifactId> |
|||
<version>${jacoco.version}</version> |
|||
<executions> |
|||
<execution> |
|||
<id>jacoco-initialize</id> |
|||
<goals> |
|||
<goal>prepare-agent</goal> |
|||
</goals> |
|||
</execution> |
|||
<execution> |
|||
<id>jacoco-site</id> |
|||
<phase>package</phase> |
|||
<goals> |
|||
<goal>report</goal> |
|||
</goals> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
|
|||
<distributionManagement> |
|||
<repository> |
|||
<id>maven-releases</id> |
|||
<name>maven-releases</name> |
|||
<url>http://3.108.254.192:8081/repository/maven-releases/</url> |
|||
</repository> |
|||
<snapshotRepository> |
|||
<id>maven-snapshots</id> |
|||
<name>maven-snapshots</name> |
|||
<url>http://3.108.254.192:8081/repository/maven-snapshots/</url> |
|||
</snapshotRepository> |
|||
</distributionManagement> |
|||
|
|||
|
|||
</project> |
@ -0,0 +1,225 @@ |
|||
#!/bin/sh |
|||
# ---------------------------------------------------------------------------- |
|||
# 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. |
|||
# ---------------------------------------------------------------------------- |
|||
|
|||
# ---------------------------------------------------------------------------- |
|||
# Maven2 Start Up Batch script |
|||
# |
|||
# Required ENV vars: |
|||
# ------------------ |
|||
# JAVA_HOME - location of a JDK home dir |
|||
# |
|||
# Optional ENV vars |
|||
# ----------------- |
|||
# M2_HOME - location of maven2's installed home dir |
|||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven |
|||
# e.g. to debug Maven itself, use |
|||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 |
|||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files |
|||
# ---------------------------------------------------------------------------- |
|||
|
|||
if [ -z "$MAVEN_SKIP_RC" ] ; then |
|||
|
|||
if [ -f /etc/mavenrc ] ; then |
|||
. /etc/mavenrc |
|||
fi |
|||
|
|||
if [ -f "$HOME/.mavenrc" ] ; then |
|||
. "$HOME/.mavenrc" |
|||
fi |
|||
|
|||
fi |
|||
|
|||
# OS specific support. $var _must_ be set to either true or false. |
|||
cygwin=false; |
|||
darwin=false; |
|||
mingw=false |
|||
case "`uname`" in |
|||
CYGWIN*) cygwin=true ;; |
|||
MINGW*) mingw=true;; |
|||
Darwin*) darwin=true |
|||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home |
|||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html |
|||
if [ -z "$JAVA_HOME" ]; then |
|||
if [ -x "/usr/libexec/java_home" ]; then |
|||
export JAVA_HOME="`/usr/libexec/java_home`" |
|||
else |
|||
export JAVA_HOME="/Library/Java/Home" |
|||
fi |
|||
fi |
|||
;; |
|||
esac |
|||
|
|||
if [ -z "$JAVA_HOME" ] ; then |
|||
if [ -r /etc/gentoo-release ] ; then |
|||
JAVA_HOME=`java-config --jre-home` |
|||
fi |
|||
fi |
|||
|
|||
if [ -z "$M2_HOME" ] ; then |
|||
## resolve links - $0 may be a link to maven's home |
|||
PRG="$0" |
|||
|
|||
# need this for relative symlinks |
|||
while [ -h "$PRG" ] ; do |
|||
ls=`ls -ld "$PRG"` |
|||
link=`expr "$ls" : '.*-> \(.*\)$'` |
|||
if expr "$link" : '/.*' > /dev/null; then |
|||
PRG="$link" |
|||
else |
|||
PRG="`dirname "$PRG"`/$link" |
|||
fi |
|||
done |
|||
|
|||
saveddir=`pwd` |
|||
|
|||
M2_HOME=`dirname "$PRG"`/.. |
|||
|
|||
# make it fully qualified |
|||
M2_HOME=`cd "$M2_HOME" && pwd` |
|||
|
|||
cd "$saveddir" |
|||
# echo Using m2 at $M2_HOME |
|||
fi |
|||
|
|||
# For Cygwin, ensure paths are in UNIX format before anything is touched |
|||
if $cygwin ; then |
|||
[ -n "$M2_HOME" ] && |
|||
M2_HOME=`cygpath --unix "$M2_HOME"` |
|||
[ -n "$JAVA_HOME" ] && |
|||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"` |
|||
[ -n "$CLASSPATH" ] && |
|||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"` |
|||
fi |
|||
|
|||
# For Migwn, ensure paths are in UNIX format before anything is touched |
|||
if $mingw ; then |
|||
[ -n "$M2_HOME" ] && |
|||
M2_HOME="`(cd "$M2_HOME"; pwd)`" |
|||
[ -n "$JAVA_HOME" ] && |
|||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" |
|||
# TODO classpath? |
|||
fi |
|||
|
|||
if [ -z "$JAVA_HOME" ]; then |
|||
javaExecutable="`which javac`" |
|||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then |
|||
# readlink(1) is not available as standard on Solaris 10. |
|||
readLink=`which readlink` |
|||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then |
|||
if $darwin ; then |
|||
javaHome="`dirname \"$javaExecutable\"`" |
|||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" |
|||
else |
|||
javaExecutable="`readlink -f \"$javaExecutable\"`" |
|||
fi |
|||
javaHome="`dirname \"$javaExecutable\"`" |
|||
javaHome=`expr "$javaHome" : '\(.*\)/bin'` |
|||
JAVA_HOME="$javaHome" |
|||
export JAVA_HOME |
|||
fi |
|||
fi |
|||
fi |
|||
|
|||
if [ -z "$JAVACMD" ] ; then |
|||
if [ -n "$JAVA_HOME" ] ; then |
|||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
|||
# IBM's JDK on AIX uses strange locations for the executables |
|||
JAVACMD="$JAVA_HOME/jre/sh/java" |
|||
else |
|||
JAVACMD="$JAVA_HOME/bin/java" |
|||
fi |
|||
else |
|||
JAVACMD="`which java`" |
|||
fi |
|||
fi |
|||
|
|||
if [ ! -x "$JAVACMD" ] ; then |
|||
echo "Error: JAVA_HOME is not defined correctly." >&2 |
|||
echo " We cannot execute $JAVACMD" >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
if [ -z "$JAVA_HOME" ] ; then |
|||
echo "Warning: JAVA_HOME environment variable is not set." |
|||
fi |
|||
|
|||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher |
|||
|
|||
# traverses directory structure from process work directory to filesystem root |
|||
# first directory with .mvn subdirectory is considered project base directory |
|||
find_maven_basedir() { |
|||
|
|||
if [ -z "$1" ] |
|||
then |
|||
echo "Path not specified to find_maven_basedir" |
|||
return 1 |
|||
fi |
|||
|
|||
basedir="$1" |
|||
wdir="$1" |
|||
while [ "$wdir" != '/' ] ; do |
|||
if [ -d "$wdir"/.mvn ] ; then |
|||
basedir=$wdir |
|||
break |
|||
fi |
|||
# workaround for JBEAP-8937 (on Solaris 10/Sparc) |
|||
if [ -d "${wdir}" ]; then |
|||
wdir=`cd "$wdir/.."; pwd` |
|||
fi |
|||
# end of workaround |
|||
done |
|||
echo "${basedir}" |
|||
} |
|||
|
|||
# concatenates all lines of a file |
|||
concat_lines() { |
|||
if [ -f "$1" ]; then |
|||
echo "$(tr -s '\n' ' ' < "$1")" |
|||
fi |
|||
} |
|||
|
|||
BASE_DIR=`find_maven_basedir "$(pwd)"` |
|||
if [ -z "$BASE_DIR" ]; then |
|||
exit 1; |
|||
fi |
|||
|
|||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} |
|||
echo $MAVEN_PROJECTBASEDIR |
|||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" |
|||
|
|||
# For Cygwin, switch paths to Windows format before running java |
|||
if $cygwin; then |
|||
[ -n "$M2_HOME" ] && |
|||
M2_HOME=`cygpath --path --windows "$M2_HOME"` |
|||
[ -n "$JAVA_HOME" ] && |
|||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` |
|||
[ -n "$CLASSPATH" ] && |
|||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"` |
|||
[ -n "$MAVEN_PROJECTBASEDIR" ] && |
|||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` |
|||
fi |
|||
|
|||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain |
|||
|
|||
exec "$JAVACMD" \ |
|||
$MAVEN_OPTS \ |
|||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ |
|||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ |
|||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" |
@ -0,0 +1,143 @@ |
|||
@REM ---------------------------------------------------------------------------- |
|||
@REM Licensed to the Apache Software Foundation (ASF) under one |
|||
@REM or more contributor license agreements. See the NOTICE file |
|||
@REM distributed with this work for additional information |
|||
@REM regarding copyright ownership. The ASF licenses this file |
|||
@REM to you under the Apache License, Version 2.0 (the |
|||
@REM "License"); you may not use this file except in compliance |
|||
@REM with the License. You may obtain a copy of the License at |
|||
@REM |
|||
@REM http://www.apache.org/licenses/LICENSE-2.0 |
|||
@REM |
|||
@REM Unless required by applicable law or agreed to in writing, |
|||
@REM software distributed under the License is distributed on an |
|||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
|||
@REM KIND, either express or implied. See the License for the |
|||
@REM specific language governing permissions and limitations |
|||
@REM under the License. |
|||
@REM ---------------------------------------------------------------------------- |
|||
|
|||
@REM ---------------------------------------------------------------------------- |
|||
@REM Maven2 Start Up Batch script |
|||
@REM |
|||
@REM Required ENV vars: |
|||
@REM JAVA_HOME - location of a JDK home dir |
|||
@REM |
|||
@REM Optional ENV vars |
|||
@REM M2_HOME - location of maven2's installed home dir |
|||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands |
|||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending |
|||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven |
|||
@REM e.g. to debug Maven itself, use |
|||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 |
|||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files |
|||
@REM ---------------------------------------------------------------------------- |
|||
|
|||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' |
|||
@echo off |
|||
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' |
|||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% |
|||
|
|||
@REM set %HOME% to equivalent of $HOME |
|||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") |
|||
|
|||
@REM Execute a user defined script before this one |
|||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre |
|||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending |
|||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" |
|||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" |
|||
:skipRcPre |
|||
|
|||
@setlocal |
|||
|
|||
set ERROR_CODE=0 |
|||
|
|||
@REM To isolate internal variables from possible post scripts, we use another setlocal |
|||
@setlocal |
|||
|
|||
@REM ==== START VALIDATION ==== |
|||
if not "%JAVA_HOME%" == "" goto OkJHome |
|||
|
|||
echo. |
|||
echo Error: JAVA_HOME not found in your environment. >&2 |
|||
echo Please set the JAVA_HOME variable in your environment to match the >&2 |
|||
echo location of your Java installation. >&2 |
|||
echo. |
|||
goto error |
|||
|
|||
:OkJHome |
|||
if exist "%JAVA_HOME%\bin\java.exe" goto init |
|||
|
|||
echo. |
|||
echo Error: JAVA_HOME is set to an invalid directory. >&2 |
|||
echo JAVA_HOME = "%JAVA_HOME%" >&2 |
|||
echo Please set the JAVA_HOME variable in your environment to match the >&2 |
|||
echo location of your Java installation. >&2 |
|||
echo. |
|||
goto error |
|||
|
|||
@REM ==== END VALIDATION ==== |
|||
|
|||
:init |
|||
|
|||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". |
|||
@REM Fallback to current working directory if not found. |
|||
|
|||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% |
|||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir |
|||
|
|||
set EXEC_DIR=%CD% |
|||
set WDIR=%EXEC_DIR% |
|||
:findBaseDir |
|||
IF EXIST "%WDIR%"\.mvn goto baseDirFound |
|||
cd .. |
|||
IF "%WDIR%"=="%CD%" goto baseDirNotFound |
|||
set WDIR=%CD% |
|||
goto findBaseDir |
|||
|
|||
:baseDirFound |
|||
set MAVEN_PROJECTBASEDIR=%WDIR% |
|||
cd "%EXEC_DIR%" |
|||
goto endDetectBaseDir |
|||
|
|||
:baseDirNotFound |
|||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR% |
|||
cd "%EXEC_DIR%" |
|||
|
|||
:endDetectBaseDir |
|||
|
|||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig |
|||
|
|||
@setlocal EnableExtensions EnableDelayedExpansion |
|||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a |
|||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% |
|||
|
|||
:endReadAdditionalConfig |
|||
|
|||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" |
|||
|
|||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" |
|||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain |
|||
|
|||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* |
|||
if ERRORLEVEL 1 goto error |
|||
goto end |
|||
|
|||
:error |
|||
set ERROR_CODE=1 |
|||
|
|||
:end |
|||
@endlocal & set ERROR_CODE=%ERROR_CODE% |
|||
|
|||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost |
|||
@REM check for post script, once with legacy .bat ending and once with .cmd ending |
|||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" |
|||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" |
|||
:skipRcPost |
|||
|
|||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' |
|||
if "%MAVEN_BATCH_PAUSE%" == "on" pause |
|||
|
|||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% |
|||
|
|||
exit /B %ERROR_CODE% |
@ -0,0 +1,23 @@ |
|||
#!/usr/bin/env bash |
|||
|
|||
CONTAINER_NAME=spring-boot-shopping-cart |
|||
echo -e "\nSet docker container name as ${CONTAINER_NAME}\n" |
|||
IMAGE_NAME=${CONTAINER_NAME}:dev |
|||
echo -e "\nSet docker image name as ${IMAGE_NAME}\n" |
|||
PORT=8070 |
|||
echo -e "Set docker image PORT to ${PORT}\n" |
|||
|
|||
echo -e "Create uber jar...\n" |
|||
mvn clean package |
|||
|
|||
echo -e "\nStop running Docker containers with image tag ${CONTAINER_NAME}, and remove them...n" |
|||
docker stop $(docker ps -a | grep ${CONTAINER_NAME} | awk '{print $1}') |
|||
docker rm $(docker ps -a | grep ${CONTAINER_NAME} | awk '{print $1}') |
|||
|
|||
echo -e "\nDocker build image with name ${IMAGE_NAME}...\n" |
|||
docker build -t ${IMAGE_NAME} -f docker/Dockerfile . |
|||
|
|||
echo -e "\nStart Docker container of the image ${IMAGE_NAME} with name ${CONTAINER_NAME}...\n" |
|||
docker run --rm -i -p ${PORT}:${PORT} \ |
|||
--name ${CONTAINER_NAME} \ |
|||
${IMAGE_NAME} |
@ -0,0 +1,12 @@ |
|||
package com.reljicd; |
|||
|
|||
import org.springframework.boot.SpringApplication; |
|||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
|||
|
|||
@SpringBootApplication |
|||
public class ShoppingCartApplication { |
|||
|
|||
public static void main(String[] args) { |
|||
SpringApplication.run(ShoppingCartApplication.class, args); |
|||
} |
|||
} |
@ -0,0 +1,33 @@ |
|||
package com.reljicd.config; |
|||
|
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.ui.Model; |
|||
import org.springframework.web.bind.annotation.ControllerAdvice; |
|||
import org.springframework.web.bind.annotation.ExceptionHandler; |
|||
import org.springframework.web.bind.annotation.ResponseStatus; |
|||
import org.springframework.web.servlet.ModelAndView; |
|||
|
|||
/** |
|||
* Global exception handler |
|||
* |
|||
* @author Dusan |
|||
*/ |
|||
@ControllerAdvice |
|||
public class GlobalExceptionHandler { |
|||
|
|||
private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); |
|||
|
|||
@ExceptionHandler(Throwable.class) |
|||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) |
|||
public ModelAndView exception(final Throwable throwable, final Model model) { |
|||
logger.error("Exception during execution of SpringSecurity application", throwable); |
|||
|
|||
ModelAndView modelAndView = new ModelAndView("/error"); |
|||
String errorMessage = (throwable != null ? throwable.toString() : "Unknown error"); |
|||
modelAndView.addObject("errorMessage", errorMessage); |
|||
return modelAndView; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,39 @@ |
|||
package com.reljicd.config; |
|||
|
|||
import org.slf4j.Logger; |
|||
import org.slf4j.LoggerFactory; |
|||
import org.springframework.security.access.AccessDeniedException; |
|||
import org.springframework.security.core.Authentication; |
|||
import org.springframework.security.core.context.SecurityContextHolder; |
|||
import org.springframework.security.web.access.AccessDeniedHandler; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import javax.servlet.ServletException; |
|||
import javax.servlet.http.HttpServletRequest; |
|||
import javax.servlet.http.HttpServletResponse; |
|||
import java.io.IOException; |
|||
|
|||
/** |
|||
* Custom 403 access denied handler |
|||
*/ |
|||
@Component |
|||
public class MyAccessDeniedHandler implements AccessDeniedHandler { |
|||
|
|||
private static Logger logger = LoggerFactory.getLogger(MyAccessDeniedHandler.class); |
|||
|
|||
@Override |
|||
public void handle(HttpServletRequest httpServletRequest, |
|||
HttpServletResponse httpServletResponse, |
|||
AccessDeniedException e) throws IOException, ServletException { |
|||
|
|||
Authentication auth |
|||
= SecurityContextHolder.getContext().getAuthentication(); |
|||
|
|||
if (auth != null) { |
|||
logger.info(String.format("User '%s' attempted to access the protected URL: %s", auth.getName(), httpServletRequest.getRequestURI())); |
|||
} |
|||
|
|||
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/403"); |
|||
|
|||
} |
|||
} |
@ -0,0 +1,105 @@ |
|||
package com.reljicd.config; |
|||
|
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; |
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; |
|||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; |
|||
import org.springframework.security.crypto.password.PasswordEncoder; |
|||
import org.springframework.security.web.access.AccessDeniedHandler; |
|||
|
|||
import javax.sql.DataSource; |
|||
|
|||
/** |
|||
* Spring Security Configuration |
|||
* http://docs.spring.io/spring-boot/docs/current/reference/html/howto-security.html
|
|||
* Switches off Spring Boot automatic security configuration |
|||
* |
|||
* @author Dusan |
|||
*/ |
|||
@Configuration |
|||
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { |
|||
|
|||
private final AccessDeniedHandler accessDeniedHandler; |
|||
|
|||
final DataSource dataSource; |
|||
|
|||
@Value("${spring.admin.username}") |
|||
private String adminUsername; |
|||
|
|||
@Value("${spring.admin.username}") |
|||
private String adminPassword; |
|||
|
|||
@Value("${spring.queries.users-query}") |
|||
private String usersQuery; |
|||
|
|||
@Value("${spring.queries.roles-query}") |
|||
private String rolesQuery; |
|||
|
|||
@Autowired |
|||
public SpringSecurityConfig(AccessDeniedHandler accessDeniedHandler, DataSource dataSource) { |
|||
this.accessDeniedHandler = accessDeniedHandler; |
|||
this.dataSource = dataSource; |
|||
} |
|||
|
|||
/** |
|||
* HTTPSecurity configurer |
|||
* - roles ADMIN allow to access /admin/** |
|||
* - roles USER allow to access /user/** and /newPost/** |
|||
* - anybody can visit /, /home, /about, /registration, /error, /blog/**, /post/**, /h2-console/** |
|||
* - every other page needs authentication |
|||
* - custom 403 access denied handler |
|||
*/ |
|||
@Override |
|||
protected void configure(HttpSecurity http) throws Exception { |
|||
|
|||
http.csrf().disable() |
|||
.authorizeRequests() |
|||
.antMatchers("/home", "/registration", "/error", "/h2-console/**").permitAll() |
|||
.anyRequest().authenticated() |
|||
.and() |
|||
.formLogin() |
|||
.loginPage("/login") |
|||
.defaultSuccessUrl("/home") |
|||
.permitAll() |
|||
.and() |
|||
.logout() |
|||
.permitAll() |
|||
.and() |
|||
.exceptionHandling().accessDeniedHandler(accessDeniedHandler) |
|||
// Fix for H2 console
|
|||
.and().headers().frameOptions().disable(); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Authentication details |
|||
*/ |
|||
@Autowired |
|||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { |
|||
|
|||
// Database authentication
|
|||
auth. |
|||
jdbcAuthentication() |
|||
.usersByUsernameQuery(usersQuery) |
|||
.authoritiesByUsernameQuery(rolesQuery) |
|||
.dataSource(dataSource) |
|||
.passwordEncoder(passwordEncoder()); |
|||
|
|||
// In memory authentication
|
|||
auth.inMemoryAuthentication() |
|||
.withUser(adminUsername).password(adminPassword).roles("ADMIN"); |
|||
} |
|||
|
|||
/** |
|||
* Configure and return BCrypt password encoder |
|||
*/ |
|||
@Bean |
|||
public PasswordEncoder passwordEncoder() { |
|||
return new BCryptPasswordEncoder(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,28 @@ |
|||
package com.reljicd.controller; |
|||
|
|||
import org.springframework.boot.autoconfigure.web.ErrorController; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RestController; |
|||
import org.springframework.web.servlet.ModelAndView; |
|||
|
|||
@RestController |
|||
public class CustomErrorController implements ErrorController { |
|||
|
|||
private static final String PATH = "/error"; |
|||
|
|||
@RequestMapping(PATH) |
|||
public ModelAndView error() { |
|||
return new ModelAndView("/error"); |
|||
} |
|||
|
|||
@GetMapping("/403") |
|||
public ModelAndView error403() { |
|||
return new ModelAndView("/403"); |
|||
} |
|||
|
|||
@Override |
|||
public String getErrorPath() { |
|||
return PATH; |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
package com.reljicd.controller; |
|||
|
|||
import com.reljicd.model.Product; |
|||
import com.reljicd.service.ProductService; |
|||
import com.reljicd.util.Pager; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.domain.Page; |
|||
import org.springframework.data.domain.PageRequest; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.RequestParam; |
|||
import org.springframework.web.servlet.ModelAndView; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
@Controller |
|||
public class HomeController { |
|||
|
|||
private static final int INITIAL_PAGE = 0; |
|||
|
|||
private final ProductService productService; |
|||
|
|||
@Autowired |
|||
public HomeController(ProductService productService) { |
|||
this.productService = productService; |
|||
} |
|||
|
|||
@GetMapping("/home") |
|||
public ModelAndView home(@RequestParam("page") Optional<Integer> page) { |
|||
|
|||
// Evaluate page. If requested parameter is null or less than 0 (to
|
|||
// prevent exception), return initial size. Otherwise, return value of
|
|||
// param. decreased by 1.
|
|||
int evalPage = (page.orElse(0) < 1) ? INITIAL_PAGE : page.get() - 1; |
|||
|
|||
Page<Product> products = productService.findAllProductsPageable(new PageRequest(evalPage, 5)); |
|||
Pager pager = new Pager(products); |
|||
|
|||
ModelAndView modelAndView = new ModelAndView(); |
|||
modelAndView.addObject("products", products); |
|||
modelAndView.addObject("pager", pager); |
|||
modelAndView.setViewName("/home"); |
|||
return modelAndView; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,19 @@ |
|||
package com.reljicd.controller; |
|||
|
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
|
|||
import java.security.Principal; |
|||
|
|||
@Controller |
|||
public class LoginController { |
|||
|
|||
@GetMapping("/login") |
|||
public String login(Principal principal) { |
|||
if (principal != null) { |
|||
return "redirect:/home"; |
|||
} |
|||
return "/login"; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,62 @@ |
|||
package com.reljicd.controller; |
|||
|
|||
import com.reljicd.model.User; |
|||
import com.reljicd.service.UserService; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.validation.BindingResult; |
|||
import org.springframework.web.bind.annotation.RequestMapping; |
|||
import org.springframework.web.bind.annotation.RequestMethod; |
|||
import org.springframework.web.servlet.ModelAndView; |
|||
|
|||
import javax.validation.Valid; |
|||
|
|||
@Controller |
|||
public class RegistrationController { |
|||
|
|||
private final UserService userService; |
|||
|
|||
@Autowired |
|||
public RegistrationController(UserService userService) { |
|||
this.userService = userService; |
|||
} |
|||
|
|||
@RequestMapping(value = "/registration", method = RequestMethod.GET) |
|||
public ModelAndView registration() { |
|||
ModelAndView modelAndView = new ModelAndView(); |
|||
User user = new User(); |
|||
modelAndView.addObject("user", user); |
|||
modelAndView.setViewName("/registration"); |
|||
return modelAndView; |
|||
} |
|||
|
|||
@RequestMapping(value = "/registration", method = RequestMethod.POST) |
|||
public ModelAndView createNewUser(@Valid User user, BindingResult bindingResult) { |
|||
|
|||
if (userService.findByEmail(user.getEmail()).isPresent()) { |
|||
bindingResult |
|||
.rejectValue("email", "error.user", |
|||
"There is already a user registered with the email provided"); |
|||
} |
|||
if (userService.findByUsername(user.getUsername()).isPresent()) { |
|||
bindingResult |
|||
.rejectValue("username", "error.user", |
|||
"There is already a user registered with the username provided"); |
|||
} |
|||
|
|||
ModelAndView modelAndView = new ModelAndView(); |
|||
|
|||
if (bindingResult.hasErrors()) { |
|||
modelAndView.setViewName("/registration"); |
|||
} else { |
|||
// Registration successful, save user
|
|||
// Set user role to USER and set it as active
|
|||
userService.saveUser(user); |
|||
|
|||
modelAndView.addObject("successMessage", "User has been registered successfully"); |
|||
modelAndView.addObject("user", new User()); |
|||
modelAndView.setViewName("/registration"); |
|||
} |
|||
return modelAndView; |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
package com.reljicd.controller; |
|||
|
|||
import com.reljicd.exception.NotEnoughProductsInStockException; |
|||
import com.reljicd.service.ProductService; |
|||
import com.reljicd.service.ShoppingCartService; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Controller; |
|||
import org.springframework.web.bind.annotation.GetMapping; |
|||
import org.springframework.web.bind.annotation.PathVariable; |
|||
import org.springframework.web.servlet.ModelAndView; |
|||
|
|||
@Controller |
|||
public class ShoppingCartController { |
|||
|
|||
private final ShoppingCartService shoppingCartService; |
|||
|
|||
private final ProductService productService; |
|||
|
|||
@Autowired |
|||
public ShoppingCartController(ShoppingCartService shoppingCartService, ProductService productService) { |
|||
this.shoppingCartService = shoppingCartService; |
|||
this.productService = productService; |
|||
} |
|||
|
|||
@GetMapping("/shoppingCart") |
|||
public ModelAndView shoppingCart() { |
|||
ModelAndView modelAndView = new ModelAndView("/shoppingCart"); |
|||
modelAndView.addObject("products", shoppingCartService.getProductsInCart()); |
|||
modelAndView.addObject("total", shoppingCartService.getTotal().toString()); |
|||
return modelAndView; |
|||
} |
|||
|
|||
@GetMapping("/shoppingCart/addProduct/{productId}") |
|||
public ModelAndView addProductToCart(@PathVariable("productId") Long productId) { |
|||
productService.findById(productId).ifPresent(shoppingCartService::addProduct); |
|||
return shoppingCart(); |
|||
} |
|||
|
|||
@GetMapping("/shoppingCart/removeProduct/{productId}") |
|||
public ModelAndView removeProductFromCart(@PathVariable("productId") Long productId) { |
|||
productService.findById(productId).ifPresent(shoppingCartService::removeProduct); |
|||
return shoppingCart(); |
|||
} |
|||
|
|||
@GetMapping("/shoppingCart/checkout") |
|||
public ModelAndView checkout() { |
|||
try { |
|||
shoppingCartService.checkout(); |
|||
} catch (NotEnoughProductsInStockException e) { |
|||
return shoppingCart().addObject("outOfStockMessage", e.getMessage()); |
|||
} |
|||
return shoppingCart(); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
package com.reljicd.exception; |
|||
|
|||
import com.reljicd.model.Product; |
|||
|
|||
public class NotEnoughProductsInStockException extends Exception { |
|||
|
|||
private static final String DEFAULT_MESSAGE = "Not enough products in stock"; |
|||
|
|||
public NotEnoughProductsInStockException() { |
|||
super(DEFAULT_MESSAGE); |
|||
} |
|||
|
|||
public NotEnoughProductsInStockException(Product product) { |
|||
super(String.format("Not enough %s products in stock. Only %d left", product.getName(), product.getQuantity())); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,88 @@ |
|||
package com.reljicd.model; |
|||
|
|||
import org.hibernate.validator.constraints.Length; |
|||
|
|||
import javax.persistence.*; |
|||
import javax.validation.constraints.DecimalMin; |
|||
import javax.validation.constraints.Min; |
|||
import java.math.BigDecimal; |
|||
|
|||
@Entity |
|||
@Table(name = "product") |
|||
public class Product { |
|||
|
|||
@Id |
|||
@GeneratedValue(strategy = GenerationType.AUTO) |
|||
@Column(name = "product_id") |
|||
private Long id; |
|||
|
|||
@Column(name = "name", nullable = false, unique = true) |
|||
@Length(min = 3, message = "*Name must have at least 5 characters") |
|||
private String name; |
|||
|
|||
@Column(name = "description") |
|||
private String description; |
|||
|
|||
@Column(name = "quantity", nullable = false) |
|||
@Min(value = 0, message = "*Quantity has to be non negative number") |
|||
private Integer quantity; |
|||
|
|||
@Column(name = "price", nullable = false) |
|||
@DecimalMin(value = "0.00", message = "*Price has to be non negative number") |
|||
private BigDecimal price; |
|||
|
|||
public Long getId() { |
|||
return id; |
|||
} |
|||
|
|||
public void setId(Long id) { |
|||
this.id = id; |
|||
} |
|||
|
|||
public String getName() { |
|||
return name; |
|||
} |
|||
|
|||
public void setName(String name) { |
|||
this.name = name; |
|||
} |
|||
|
|||
public String getDescription() { |
|||
return description; |
|||
} |
|||
|
|||
public void setDescription(String description) { |
|||
this.description = description; |
|||
} |
|||
|
|||
public Integer getQuantity() { |
|||
return quantity; |
|||
} |
|||
|
|||
public void setQuantity(Integer quantity) { |
|||
this.quantity = quantity; |
|||
} |
|||
|
|||
public BigDecimal getPrice() { |
|||
return price; |
|||
} |
|||
|
|||
public void setPrice(BigDecimal unitPrice) { |
|||
this.price = unitPrice; |
|||
} |
|||
|
|||
@Override |
|||
public boolean equals(Object o) { |
|||
if (this == o) return true; |
|||
if (o == null || getClass() != o.getClass()) return false; |
|||
|
|||
Product product = (Product) o; |
|||
|
|||
return id.equals(product.id); |
|||
} |
|||
|
|||
@Override |
|||
public int hashCode() { |
|||
return id.hashCode(); |
|||
} |
|||
} |
@ -0,0 +1,44 @@ |
|||
package com.reljicd.model; |
|||
|
|||
import javax.persistence.*; |
|||
import java.util.Collection; |
|||
|
|||
@Entity |
|||
@Table(name = "role") |
|||
public class Role { |
|||
|
|||
@Id |
|||
@GeneratedValue(strategy = GenerationType.AUTO) |
|||
@Column(name = "role_id") |
|||
private Long id; |
|||
|
|||
@Column(name = "role", unique = true) |
|||
private String role; |
|||
|
|||
@ManyToMany(cascade = CascadeType.ALL, mappedBy = "roles") |
|||
private Collection<User> users; |
|||
|
|||
public Long getId() { |
|||
return id; |
|||
} |
|||
|
|||
public void setId(Long id) { |
|||
this.id = id; |
|||
} |
|||
|
|||
public String getRole() { |
|||
return role; |
|||
} |
|||
|
|||
public void setRole(String role) { |
|||
this.role = role; |
|||
} |
|||
|
|||
public Collection<User> getUsers() { |
|||
return users; |
|||
} |
|||
|
|||
public void setUsers(Collection<User> users) { |
|||
this.users = users; |
|||
} |
|||
} |
@ -0,0 +1,114 @@ |
|||
package com.reljicd.model; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonIgnore; |
|||
import org.hibernate.validator.constraints.Email; |
|||
import org.hibernate.validator.constraints.Length; |
|||
import org.hibernate.validator.constraints.NotEmpty; |
|||
|
|||
import javax.persistence.*; |
|||
import java.util.Collection; |
|||
|
|||
@Entity |
|||
@Table(name = "user") |
|||
public class User { |
|||
|
|||
@Id |
|||
@GeneratedValue(strategy = GenerationType.AUTO) |
|||
@Column(name = "user_id") |
|||
private Long id; |
|||
|
|||
@Column(name = "email", unique = true, nullable = false) |
|||
@Email(message = "*Please provide a valid Email") |
|||
@NotEmpty(message = "*Please provide an email") |
|||
private String email; |
|||
|
|||
@Column(name = "password", nullable = false) |
|||
@Length(min = 5, message = "*Your password must have at least 5 characters") |
|||
@NotEmpty(message = "*Please provide your password") |
|||
@JsonIgnore |
|||
private String password; |
|||
|
|||
@Column(name = "username", nullable = false, unique = true) |
|||
@Length(min = 5, message = "*Your username must have at least 5 characters") |
|||
@NotEmpty(message = "*Please provide your name") |
|||
private String username; |
|||
|
|||
@Column(name = "name") |
|||
@NotEmpty(message = "*Please provide your name") |
|||
private String name; |
|||
|
|||
@Column(name = "last_name") |
|||
@NotEmpty(message = "*Please provide your last name") |
|||
private String lastName; |
|||
|
|||
@Column(name = "active", nullable = false) |
|||
private int active; |
|||
|
|||
@ManyToMany(cascade = CascadeType.ALL) |
|||
@JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) |
|||
private Collection<Role> roles; |
|||
|
|||
public Long getId() { |
|||
return id; |
|||
} |
|||
|
|||
public void setId(Long id) { |
|||
this.id = id; |
|||
} |
|||
|
|||
public String getPassword() { |
|||
return password; |
|||
} |
|||
|
|||
public void setPassword(String password) { |
|||
this.password = password; |
|||
} |
|||
|
|||
public String getUsername() { |
|||
return username; |
|||
} |
|||
|
|||
public void setUsername(String username) { |
|||
this.username = username; |
|||
} |
|||
|
|||
public String getName() { |
|||
return name; |
|||
} |
|||
|
|||
public void setName(String name) { |
|||
this.name = name; |
|||
} |
|||
|
|||
public String getLastName() { |
|||
return lastName; |
|||
} |
|||
|
|||
public void setLastName(String lastName) { |
|||
this.lastName = lastName; |
|||
} |
|||
|
|||
public String getEmail() { |
|||
return email; |
|||
} |
|||
|
|||
public void setEmail(String email) { |
|||
this.email = email; |
|||
} |
|||
|
|||
public int getActive() { |
|||
return active; |
|||
} |
|||
|
|||
public void setActive(int active) { |
|||
this.active = active; |
|||
} |
|||
|
|||
public Collection<Role> getRoles() { |
|||
return roles; |
|||
} |
|||
|
|||
public void setRoles(Collection<Role> roles) { |
|||
this.roles = roles; |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
package com.reljicd.repository; |
|||
|
|||
import com.reljicd.model.Product; |
|||
import org.springframework.data.jpa.repository.JpaRepository; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
public interface ProductRepository extends JpaRepository<Product, Long> { |
|||
Optional<Product> findById(Long id); |
|||
} |
@ -0,0 +1,9 @@ |
|||
package com.reljicd.repository; |
|||
|
|||
import com.reljicd.model.Role; |
|||
import org.springframework.data.jpa.repository.JpaRepository; |
|||
import org.springframework.data.repository.query.Param; |
|||
|
|||
public interface RoleRepository extends JpaRepository<Role, Long> { |
|||
Role findByRole(@Param("role") String role); |
|||
} |
@ -0,0 +1,13 @@ |
|||
package com.reljicd.repository; |
|||
|
|||
import com.reljicd.model.User; |
|||
import org.springframework.data.jpa.repository.JpaRepository; |
|||
import org.springframework.data.repository.query.Param; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
public interface UserRepository extends JpaRepository<User, Long> { |
|||
Optional<User> findByEmail(@Param("email") String email); |
|||
|
|||
Optional<User> findByUsername(@Param("username") String username); |
|||
} |
@ -0,0 +1,15 @@ |
|||
package com.reljicd.service; |
|||
|
|||
import com.reljicd.model.Product; |
|||
import org.springframework.data.domain.Page; |
|||
import org.springframework.data.domain.Pageable; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
public interface ProductService { |
|||
|
|||
Optional<Product> findById(Long id); |
|||
|
|||
Page<Product> findAllProductsPageable(Pageable pageable); |
|||
|
|||
} |
@ -0,0 +1,20 @@ |
|||
package com.reljicd.service; |
|||
|
|||
import com.reljicd.exception.NotEnoughProductsInStockException; |
|||
import com.reljicd.model.Product; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.util.Map; |
|||
|
|||
public interface ShoppingCartService { |
|||
|
|||
void addProduct(Product product); |
|||
|
|||
void removeProduct(Product product); |
|||
|
|||
Map<Product, Integer> getProductsInCart(); |
|||
|
|||
void checkout() throws NotEnoughProductsInStockException; |
|||
|
|||
BigDecimal getTotal(); |
|||
} |
@ -0,0 +1,15 @@ |
|||
package com.reljicd.service; |
|||
|
|||
import com.reljicd.model.User; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
public interface UserService { |
|||
|
|||
Optional<User> findByUsername(String username); |
|||
|
|||
Optional<User> findByEmail(String email); |
|||
|
|||
User saveUser(User user); |
|||
|
|||
} |
@ -0,0 +1,32 @@ |
|||
package com.reljicd.service.impl; |
|||
|
|||
import com.reljicd.model.Product; |
|||
import com.reljicd.repository.ProductRepository; |
|||
import com.reljicd.service.ProductService; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.data.domain.Page; |
|||
import org.springframework.data.domain.Pageable; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.util.Optional; |
|||
|
|||
@Service |
|||
public class ProductServiceImpl implements ProductService { |
|||
|
|||
private final ProductRepository productRepository; |
|||
|
|||
@Autowired |
|||
public ProductServiceImpl(ProductRepository productRepository) { |
|||
this.productRepository = productRepository; |
|||
} |
|||
|
|||
@Override |
|||
public Page<Product> findAllProductsPageable(Pageable pageable) { |
|||
return productRepository.findAll(pageable); |
|||
} |
|||
|
|||
@Override |
|||
public Optional<Product> findById(Long id) { |
|||
return productRepository.findById(id); |
|||
} |
|||
} |
@ -0,0 +1,105 @@ |
|||
package com.reljicd.service.impl; |
|||
|
|||
import com.reljicd.exception.NotEnoughProductsInStockException; |
|||
import com.reljicd.model.Product; |
|||
import com.reljicd.repository.ProductRepository; |
|||
import com.reljicd.service.ShoppingCartService; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.context.annotation.Scope; |
|||
import org.springframework.context.annotation.ScopedProxyMode; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
import org.springframework.web.context.WebApplicationContext; |
|||
|
|||
import java.math.BigDecimal; |
|||
import java.util.Collections; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
/** |
|||
* Shopping Cart is implemented with a Map, and as a session bean |
|||
* |
|||
* @author Dusan |
|||
*/ |
|||
@Service |
|||
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) |
|||
@Transactional |
|||
public class ShoppingCartServiceImpl implements ShoppingCartService { |
|||
|
|||
private final ProductRepository productRepository; |
|||
|
|||
private Map<Product, Integer> products = new HashMap<>(); |
|||
|
|||
@Autowired |
|||
public ShoppingCartServiceImpl(ProductRepository productRepository) { |
|||
this.productRepository = productRepository; |
|||
} |
|||
|
|||
/** |
|||
* If product is in the map just increment quantity by 1. |
|||
* If product is not in the map with, add it with quantity 1 |
|||
* |
|||
* @param product |
|||
*/ |
|||
@Override |
|||
public void addProduct(Product product) { |
|||
if (products.containsKey(product)) { |
|||
products.replace(product, products.get(product) + 1); |
|||
} else { |
|||
products.put(product, 1); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* If product is in the map with quantity > 1, just decrement quantity by 1. |
|||
* If product is in the map with quantity 1, remove it from map |
|||
* |
|||
* @param product |
|||
*/ |
|||
@Override |
|||
public void removeProduct(Product product) { |
|||
if (products.containsKey(product)) { |
|||
if (products.get(product) > 1) |
|||
products.replace(product, products.get(product) - 1); |
|||
else if (products.get(product) == 1) { |
|||
products.remove(product); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @return unmodifiable copy of the map |
|||
*/ |
|||
@Override |
|||
public Map<Product, Integer> getProductsInCart() { |
|||
return Collections.unmodifiableMap(products); |
|||
} |
|||
|
|||
/** |
|||
* Checkout will rollback if there is not enough of some product in stock |
|||
* |
|||
* @throws NotEnoughProductsInStockException |
|||
*/ |
|||
@Override |
|||
public void checkout() throws NotEnoughProductsInStockException { |
|||
Product product; |
|||
for (Map.Entry<Product, Integer> entry : products.entrySet()) { |
|||
// Refresh quantity for every product before checking
|
|||
product = productRepository.findOne(entry.getKey().getId()); |
|||
if (product.getQuantity() < entry.getValue()) |
|||
throw new NotEnoughProductsInStockException(product); |
|||
entry.getKey().setQuantity(product.getQuantity() - entry.getValue()); |
|||
} |
|||
productRepository.save(products.keySet()); |
|||
productRepository.flush(); |
|||
products.clear(); |
|||
} |
|||
|
|||
@Override |
|||
public BigDecimal getTotal() { |
|||
return products.entrySet().stream() |
|||
.map(entry -> entry.getKey().getPrice().multiply(BigDecimal.valueOf(entry.getValue()))) |
|||
.reduce(BigDecimal::add) |
|||
.orElse(BigDecimal.ZERO); |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
package com.reljicd.service.impl; |
|||
|
|||
import com.reljicd.model.User; |
|||
import com.reljicd.repository.RoleRepository; |
|||
import com.reljicd.repository.UserRepository; |
|||
import com.reljicd.service.UserService; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.security.crypto.password.PasswordEncoder; |
|||
import org.springframework.stereotype.Service; |
|||
|
|||
import java.util.Collections; |
|||
import java.util.Optional; |
|||
|
|||
@Service |
|||
public class UserServiceImp implements UserService { |
|||
|
|||
private final UserRepository userRepository; |
|||
private final RoleRepository roleRepository; |
|||
private final PasswordEncoder passwordEncoder; |
|||
|
|||
private static final String USER_ROLE = "ROLE_USER"; |
|||
|
|||
@Autowired |
|||
public UserServiceImp(UserRepository userRepository, RoleRepository roleRepository, PasswordEncoder passwordEncoder) { |
|||
this.userRepository = userRepository; |
|||
this.roleRepository = roleRepository; |
|||
this.passwordEncoder = passwordEncoder; |
|||
} |
|||
|
|||
@Override |
|||
public Optional<User> findByUsername(String username) { |
|||
return userRepository.findByUsername(username); |
|||
} |
|||
|
|||
@Override |
|||
public Optional<User> findByEmail(String email) { |
|||
return userRepository.findByEmail(email); |
|||
} |
|||
|
|||
@Override |
|||
public User saveUser(User user) { |
|||
// Encode plaintext password
|
|||
user.setPassword(passwordEncoder.encode(user.getPassword())); |
|||
user.setActive(1); |
|||
// Set Role to ROLE_USER
|
|||
user.setRoles(Collections.singletonList(roleRepository.findByRole(USER_ROLE))); |
|||
return userRepository.saveAndFlush(user); |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
package com.reljicd.util; |
|||
|
|||
|
|||
import com.reljicd.model.Product; |
|||
import org.springframework.data.domain.Page; |
|||
|
|||
/** |
|||
* @author Dusan Raljic |
|||
*/ |
|||
public class Pager { |
|||
|
|||
private final Page<Product> products; |
|||
|
|||
public Pager(Page<Product> products) { |
|||
this.products = products; |
|||
} |
|||
|
|||
public int getPageIndex() { |
|||
return products.getNumber() + 1; |
|||
} |
|||
|
|||
public int getPageSize() { |
|||
return products.getSize(); |
|||
} |
|||
|
|||
public boolean hasNext() { |
|||
return products.hasNext(); |
|||
} |
|||
|
|||
public boolean hasPrevious() { |
|||
return products.hasPrevious(); |
|||
} |
|||
|
|||
public int getTotalPages() { |
|||
return products.getTotalPages(); |
|||
} |
|||
|
|||
public long getTotalElements() { |
|||
return products.getTotalElements(); |
|||
} |
|||
|
|||
public boolean indexOutOfBounds() { |
|||
return this.getPageIndex() < 0 || this.getPageIndex() > this.getTotalElements(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,28 @@ |
|||
#spring.profiles.active=production |
|||
################################################## |
|||
server.port=8070 |
|||
################################################## |
|||
# define H2 DataSrouce properties |
|||
spring.datasource.url=jdbc:h2:mem:shopping_cart_db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE |
|||
spring.datasource.username=sa |
|||
spring.datasource.password= |
|||
#spring.datasource.driver-class-name=org.h2.Driver |
|||
#spring.datasource.platform=h2 |
|||
spring.datasource.data=classpath:/sql/import-h2.sql |
|||
################################################## |
|||
# enable H2 web console and set url for web console |
|||
# http://localhost:8090/h2-console |
|||
spring.h2.console.enabled=true |
|||
spring.h2.console.path=/h2-console |
|||
################################################## |
|||
# Spring Security |
|||
# Queries for AuthenticationManagerBuilder |
|||
spring.queries.users-query=select username, password, active from user where username=? |
|||
spring.queries.roles-query=select u.username, r.role from user u inner join user_role ur on(u.user_id=ur.user_id) inner join role r on(ur.role_id=r.role_id) where u.username=? |
|||
# Admin username and password |
|||
spring.admin.username=admin |
|||
spring.admin.password=admin |
|||
################################################## |
|||
# Thymeleaf |
|||
spring.thymeleaf.cache=false |
|||
spring.thymeleaf.prefix=classpath:/templates |
@ -0,0 +1,48 @@ |
|||
-- password in plaintext: "password" |
|||
INSERT INTO USER (user_id, password, email, username, name, last_name, active) |
|||
VALUES |
|||
(1, '$2a$06$OAPObzhRdRXBCbk7Hj/ot.jY3zPwR8n7/mfLtKIgTzdJa4.6TwsIm', 'user@mail.com', 'user', 'Name', 'Surname', |
|||
1); |
|||
-- password in plaintext: "password" |
|||
INSERT INTO USER (user_id, password, email, username, name, last_name, active) |
|||
VALUES |
|||
(2, '$2a$06$OAPObzhRdRXBCbk7Hj/ot.jY3zPwR8n7/mfLtKIgTzdJa4.6TwsIm', 'johndoe@gmail.com', 'johndoe', 'John', 'Doe', 1); |
|||
-- password in plaintext: "password" |
|||
INSERT INTO USER (user_id, password, email, username, name, last_name, active) |
|||
VALUES (3, '$2a$06$OAPObzhRdRXBCbk7Hj/ot.jY3zPwR8n7/mfLtKIgTzdJa4.6TwsIm', 'name@gmail.com', 'namesurname', 'Name', |
|||
'Surname', 1); |
|||
|
|||
INSERT INTO ROLE (role_id, role) |
|||
VALUES (1, 'ROLE_ADMIN'); |
|||
INSERT INTO ROLE (role_id, role) |
|||
VALUES (2, 'ROLE_USER'); |
|||
|
|||
INSERT INTO USER_ROLE (user_id, role_id) |
|||
VALUES (1, 1); |
|||
INSERT INTO USER_ROLE (user_id, role_id) |
|||
VALUES (1, 2); |
|||
INSERT INTO USER_ROLE (user_id, role_id) |
|||
VALUES (2, 2); |
|||
INSERT INTO USER_ROLE (user_id, role_id) |
|||
VALUES (3, 2); |
|||
|
|||
INSERT INTO PRODUCT (name, description, quantity, price) |
|||
VALUES ('Soap', 'Pears baby soap for Kids', 1, 35.75); |
|||
INSERT INTO PRODUCT (name, description, quantity, price) |
|||
VALUES ('Tooth Brush', 'Signal Tooth Brushes Size in (L, M, S)', 5, 34.50); |
|||
INSERT INTO PRODUCT (name, description, quantity, price) |
|||
VALUES ('Shirt', 'Casual Shirt imported from France', 3, 1500.00); |
|||
INSERT INTO PRODUCT (name, description, quantity, price) |
|||
VALUES ('Office Bag', 'Leather bag imported from USA', 40, 1000.00); |
|||
INSERT INTO PRODUCT (name, description, quantity, price) |
|||
VALUES ('Bottle', 'Hot Water Bottles', 80, 450.45); |
|||
INSERT INTO PRODUCT (name, description, quantity, price) |
|||
VALUES ('Wrist Watch', 'Imported wrist watches from swiss', 800, 2500.00); |
|||
INSERT INTO PRODUCT (name, description, quantity, price) |
|||
VALUES ('Mobile Phone', '3G/4G capability', 700, 45000.00); |
|||
INSERT INTO PRODUCT (name, description, quantity, price) |
|||
VALUES ('Shampoo', 'Head and Shoulders Shampoo', 500, 300.00); |
|||
INSERT INTO PRODUCT (name, description, quantity, price) |
|||
VALUES ('Leather Wallets', 'Imported Leather Wallets from AUS', 1000, 500.00); |
|||
INSERT INTO PRODUCT (name, description, quantity, price) |
|||
VALUES ('Camera', 'Imported Canon camera from USA', 10, 85000.00); |
@ -0,0 +1,31 @@ |
|||
h1 { |
|||
color: #0000FF; |
|||
} |
|||
|
|||
h2 { |
|||
color: #FF0000; |
|||
} |
|||
|
|||
footer { |
|||
margin-top: 60px; |
|||
} |
|||
|
|||
.validation-message { |
|||
font-style: normal; |
|||
font-size: 10px; |
|||
color: #FF1C19; |
|||
} |
|||
|
|||
a { |
|||
transition: all .2s; |
|||
} |
|||
|
|||
a:hover, a:focus, a:active { |
|||
color: purple; |
|||
text-decoration: none; |
|||
} |
|||
|
|||
.pagination { |
|||
display: block; |
|||
text-align: center; |
|||
} |
@ -0,0 +1,24 @@ |
|||
<!DOCTYPE HTML> |
|||
<html xmlns:th="http://www.thymeleaf.org"> |
|||
|
|||
<head> |
|||
<div th:replace="/fragments/header :: header"/> |
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<div th:replace="/fragments/header :: navbar"/> |
|||
|
|||
<div class="container"> |
|||
<div class="starter-template"> |
|||
<h1>403 - Access is denied</h1> |
|||
<div th:inline="text">Hello '[[${#httpServletRequest.remoteUser}]]', |
|||
you do not have permission to access this page. |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div th:replace="/fragments/footer :: footer"/> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,21 @@ |
|||
<!DOCTYPE HTML> |
|||
<html xmlns:th="http://www.thymeleaf.org"> |
|||
|
|||
<head> |
|||
<div th:replace="/fragments/header :: header"/> |
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<div th:replace="/fragments/header :: navbar"/> |
|||
|
|||
<div class="container"> |
|||
<h1 th:if="${errorMessage}" th:utext="${errorMessage}">Error java.lang.NullPointerException</h1> |
|||
<h1 th:if="!${errorMessage}">404 - Page does not exist</h1> |
|||
<a href="index.html" th:href="@{/home}">Back to Home Page</a> |
|||
</div> |
|||
|
|||
<div th:replace="/fragments/footer :: footer"/> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,36 @@ |
|||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" |
|||
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> |
|||
|
|||
<head> |
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<div th:fragment="footer"> |
|||
|
|||
<hr/> |
|||
|
|||
<div class="container"> |
|||
<div class="row"> |
|||
<div class="col-sm-12 text-center"> |
|||
<span sec:authorize="isAuthenticated()"> |
|||
| Logged user: <span sec:authentication="name"></span> | |
|||
Roles: <span sec:authentication="principal.authorities"></span> | |
|||
<a th:href="@{/logout}">Sign Out</a> |
|||
</span> |
|||
|
|||
<div> |
|||
<p class="text-center">© 2017 Dusan Reljic</p> |
|||
</div> |
|||
|
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<script type="text/javascript" |
|||
src="webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script> |
|||
|
|||
</div> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,56 @@ |
|||
<html xmlns:th="http://www.thymeleaf.org" |
|||
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> |
|||
|
|||
<head> |
|||
|
|||
<!-- this is header --> |
|||
<div th:fragment="header"> |
|||
<title th:attr="data-custom=#{thymeleaf.app.title}">Shop</title> |
|||
<!--TODO proveriti jel moguce ubaciti ovu vrednost--> |
|||
|
|||
<link href="http://cdn.jsdelivr.net/webjars/bootstrap/3.3.4/css/bootstrap.min.css" |
|||
th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.min.css}" |
|||
rel="stylesheet" media="screen"/> |
|||
|
|||
<script src="http://cdn.jsdelivr.net/webjars/jquery/2.1.4/jquery.min.js" |
|||
th:src="@{/webjars/jquery/2.1.4/jquery.min.js}"></script> |
|||
|
|||
<link rel="stylesheet" th:href="@{/css/main.css}" |
|||
href="../../css/main.css"/> |
|||
</div> |
|||
|
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<!-- this is header --> |
|||
<div th:fragment="navbar"> |
|||
<nav class="navbar navbar-inverse"> |
|||
<div class="container"> |
|||
<div class="navbar-header"> |
|||
<a class="navbar-brand" th:href="@{/home}">Shop</a> |
|||
</div> |
|||
<div id="navbar" class="collapse navbar-collapse navbar-right"> |
|||
<!-- show shoppingCart only if user is not yet authenticated --> |
|||
<ul class="nav navbar-nav" sec:authorize="isAuthenticated()"> |
|||
<li class="active"><a th:href="@{/shoppingCart}">Shopping Cart</a></li> |
|||
</ul> |
|||
<!-- show registration only if user is not yet authenticated --> |
|||
<ul class="nav navbar-nav" sec:authorize="!isAuthenticated()"> |
|||
<li class="active"><a th:href="@{/registration}">Registration</a></li> |
|||
</ul> |
|||
<!-- show login only if user is not yet authenticated --> |
|||
<ul class="nav navbar-nav" sec:authorize="!isAuthenticated()"> |
|||
<li class="active"><a th:href="@{/login}">Login</a></li> |
|||
</ul> |
|||
<!-- show sign out only if user is authenticated --> |
|||
<ul class="nav navbar-nav" sec:authorize="isAuthenticated()"> |
|||
<li class="active"><a th:href="@{/logout}">Sign Out</a></li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</nav> |
|||
</div> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,38 @@ |
|||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> |
|||
<head> |
|||
</head> |
|||
<body> |
|||
<div th:fragment="pagination"> |
|||
|
|||
<!-- Pagination --> |
|||
<div class="pagination" th:with="baseUrl=${URLparameter}"> |
|||
<span th:if="${pager.indexOutOfBounds()}"> |
|||
Page is out of bounds. Go back to <a class="pageLink" |
|||
th:href="@{${baseUrl}(page=1)}">Home</a>. |
|||
</span> |
|||
|
|||
<span th:unless="${pager.indexOutOfBounds()}"> |
|||
|
|||
<span th:if="${pager.hasPrevious()}"> |
|||
<a class="pageLink" |
|||
th:href="@{${baseUrl}(page=1)}">« first</a> |
|||
<a class="pageLink" |
|||
th:href="@{${baseUrl}(page=${pager.getPageIndex() - 1})}"> previous</a> |
|||
</span> |
|||
|
|||
<span th:if="${pager.getTotalPages() != 1}" |
|||
th:text="'Page ' + ${pager.getPageIndex()} + ' of ' + ${pager.getTotalPages()} + '.'"> |
|||
</span> |
|||
|
|||
<span th:if="${pager.hasNext()}"> |
|||
<a class="pageLink" |
|||
th:href="@{${baseUrl}(page=${pager.getPageIndex() + 1})}">next</a> |
|||
<a class="pageLink" |
|||
th:href="@{${baseUrl}(page=${pager.getTotalPages()})}">last »</a> |
|||
</span> |
|||
</span> |
|||
</div> |
|||
|
|||
</div> |
|||
</body> |
|||
</html> |
@ -0,0 +1,28 @@ |
|||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" |
|||
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> |
|||
|
|||
<head> |
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<div th:fragment="products"> |
|||
<div class="panel-default well" th:each="product : ${products}"> |
|||
<div class="panel-heading"> |
|||
<h1 th:text="${product.name}"></h1> |
|||
</div> |
|||
<h3 th:text="${product.description}" class="panel-body">Description</h3> |
|||
<div class="row panel-footer"> |
|||
<div th:inline="text" class="col-md-2">Price: [[${product.price}]] $</div> |
|||
<div th:inline="text" class="col-md-9">In Stock: [[${product.quantity}]]</div> |
|||
<a th:href="@{'/shoppingCart/addProduct/{id}'(id=${product.id})}" class="col-md-1" |
|||
sec:authorize="isAuthenticated()" th:if="${product.quantity}>0"> |
|||
<button type="button" class="btn btn-primary" th:text="Buy">Buy</button> |
|||
</a> |
|||
</div> |
|||
<br></br> |
|||
</div> |
|||
</div> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,23 @@ |
|||
<!DOCTYPE HTML> |
|||
<html xmlns:th="http://www.thymeleaf.org"> |
|||
|
|||
<head> |
|||
<div th:replace="/fragments/header :: header"/> |
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<div th:replace="/fragments/header :: navbar"/> |
|||
|
|||
<div class="container"> |
|||
|
|||
<div th:replace="/fragments/products :: products"/> |
|||
|
|||
<div th:replace="/fragments/pagination :: pagination(URLparameter='/home')"/> |
|||
|
|||
</div> |
|||
|
|||
<div th:replace="/fragments/footer :: footer"/> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,57 @@ |
|||
<!DOCTYPE html> |
|||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> |
|||
|
|||
<head> |
|||
<div th:replace="/fragments/header :: header"/> |
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<div th:replace="/fragments/header :: navbar"/> |
|||
|
|||
<div class="container"> |
|||
|
|||
<div class="row" style="margin-top:20px"> |
|||
<div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3"> |
|||
<form th:action="@{/login}" method="post"> |
|||
<fieldset> |
|||
|
|||
<div th:if="${param.error}"> |
|||
<div class="alert alert-danger"> |
|||
Invalid username and password. |
|||
</div> |
|||
</div> |
|||
|
|||
<div th:if="${param.logout}"> |
|||
<div class="alert alert-info"> |
|||
You have been logged out. |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<input type="text" name="username" id="username" class="form-control input-lg" |
|||
placeholder="UserName" required="true" autofocus="true"/> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<input type="password" name="password" id="password" class="form-control input-lg" |
|||
placeholder="Password" required="true"/> |
|||
</div> |
|||
|
|||
<div class="row"> |
|||
<div class="col-sm-3" style="float: none; margin: 0 auto;"> |
|||
<input type="submit" class="btn btn-primary btn-block" value="Login"/> |
|||
</div> |
|||
</div> |
|||
|
|||
</fieldset> |
|||
</form> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
<div th:replace="/fragments/footer :: footer"/> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,73 @@ |
|||
<!DOCTYPE HTML> |
|||
<html xmlns:th="http://www.thymeleaf.org"> |
|||
|
|||
<head> |
|||
<div th:replace="/fragments/header :: header"/> |
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<div th:replace="/fragments/header :: navbar"/> |
|||
|
|||
|
|||
<div class="container"> |
|||
|
|||
<!--Show this info message only if registered successfully--> |
|||
<div class="alert alert-info" th:if="${successMessage}" th:utext="${successMessage}"></div> |
|||
|
|||
<div class="row" style="margin-top:20px"> |
|||
<div class="col-xs-12 col-sm-8 col-md-6 col-sm-offset-2 col-md-offset-3"> |
|||
<form autocomplete="off" action="#" th:action="@{/registration}" |
|||
th:object="${user}" method="post" role="form"> |
|||
|
|||
<div class="form-group"> |
|||
<label th:if="${#fields.hasErrors('name')}" th:errors="*{name}" |
|||
class="alert alert-danger"></label> |
|||
<input type="text" th:field="*{name}" placeholder="Name" |
|||
class="form-control input-lg"/> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" |
|||
class="alert alert-danger"></label> |
|||
<input type="text" th:field="*{lastName}" placeholder="Last Name" |
|||
class="form-control input-lg"/> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label th:if="${#fields.hasErrors('email')}" th:errors="*{email}" |
|||
class="alert alert-danger"></label> |
|||
<input type="text" th:field="*{email}" placeholder="Email" |
|||
class="form-control input-lg"/> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label th:if="${#fields.hasErrors('password')}" th:errors="*{password}" |
|||
class="alert alert-danger"></label> |
|||
<input type="password" th:field="*{password}" placeholder="Password" |
|||
class="form-control input-lg"/> |
|||
</div> |
|||
|
|||
<div class="form-group"> |
|||
<label th:if="${#fields.hasErrors('username')}" th:errors="*{username}" |
|||
class="alert alert-danger"></label> |
|||
<input type="text" th:field="*{username}" placeholder="Username" |
|||
class="form-control input-lg"/> |
|||
</div> |
|||
|
|||
<div class="row"> |
|||
<div class="col-sm-3" style="float: none; margin: 0 auto;"> |
|||
<input type="submit" class="btn btn-primary btn-block" value="Submit"/> |
|||
</div> |
|||
</div> |
|||
|
|||
</form> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
<div th:replace="/fragments/footer :: footer"/> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,48 @@ |
|||
<!DOCTYPE HTML> |
|||
<html xmlns:th="http://www.thymeleaf.org" |
|||
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> |
|||
|
|||
<head> |
|||
<div th:replace="/fragments/header :: header"/> |
|||
</head> |
|||
|
|||
<body> |
|||
|
|||
<div th:replace="/fragments/header :: navbar"/> |
|||
|
|||
<div class="container"> |
|||
|
|||
<h1 class="jumbotron"> |
|||
<span sec:authentication="name"></span>'s Shopping Cart |
|||
</h1> |
|||
|
|||
<div class="alert alert-info" th:if="${outOfStockMessage}" th:utext="${outOfStockMessage}"></div> |
|||
|
|||
<div class="panel-default well" th:each="product : ${products.entrySet()}"> |
|||
<div class="panel-heading"> |
|||
<h1><a th:text="${product.getKey().name}" th:href="@{'/product/' + ${product.getKey().id}}">Title</a></h1> |
|||
<h3 th:text="${product.getKey().description}">Description</h3> |
|||
</div> |
|||
<div class="row panel-body"> |
|||
<div th:inline="text" class="col-md-2">Price: [[${product.getKey().price}]] $</div> |
|||
<div th:inline="text" class="col-md-9">Quantity: [[${product.getValue()}]]</div> |
|||
<a th:href="@{'/shoppingCart/removeProduct/{id}'(id=${product.getKey().id})}" class="col-md-1"> |
|||
<button type="button" class="btn btn-primary" th:text="Remove">Remove</button> |
|||
</a> |
|||
</div> |
|||
<br></br> |
|||
</div> |
|||
|
|||
<div class="row panel-body"> |
|||
<h2 class="col-md-11" th:inline="text">Total: [[${total}]]</h2> |
|||
<a th:href="@{'/shoppingCart/checkout'}" class="col-md-1"> |
|||
<button type="button" class="btn btn-danger" th:text="Checkout">Checkout</button> |
|||
</a> |
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
<div th:replace="/fragments/footer :: footer"/> |
|||
|
|||
</body> |
|||
</html> |
@ -0,0 +1,16 @@ |
|||
package com.reljicd; |
|||
|
|||
import org.junit.Test; |
|||
import org.junit.runner.RunWith; |
|||
import org.springframework.boot.test.context.SpringBootTest; |
|||
import org.springframework.test.context.junit4.SpringRunner; |
|||
|
|||
@RunWith(SpringRunner.class) |
|||
@SpringBootTest |
|||
public class ShoppingCartApplicationTests { |
|||
|
|||
@Test |
|||
public void contextLoads() { |
|||
} |
|||
|
|||
} |
Loading…
Reference in new issue