diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..dec0ebf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea/ +*.iml +target +.classpath +.project +.settings/ +logs/ +error-reports/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..76219841 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +#CONTRIBUTING + +We welcome new contributors. Here you can read our guidelines to contribute to the IRIS project. + +## Found a bug + +If you found a bug in our software or mistakes in the documentation there are two ways to help us: + +1. [Submit an issue](#submitting-an-issue) which explains the bug to our [GitHub Repository](https://github.com/Contargo/iris/issues/). +2. [Submit a pull request](#pull-requests) that fixes the bug. + + +## Want a new Feature + +If you want a new Feature in IRIS there are two ways to get it done: + +1. Request the new feature by [submitting an issue](#submitting-an-issue) to our [GitHub Repository](https://github.com/Contargo/iris/issues/). +2. [Submit a Pull Request](#pull-requests) that contains the new feature. + + +## Submission Guidelines + +### Submitting an Issue + +Before you submit an issue check the [GitHub Repository](https://github.com/Contargo/iris/issues/) to see if someone else reported the same issue. + +If you submit a bug, provide as much information as needed for us to reproduce the bug. + + +### Pull Requests + +Before submitting a pull request do following things: + +* If you are not familiar with GitHubs pull requests take a look at their documentation of [Using pull requests](https://help.github.com/articles/using-pull-requests/). +* Search our [GitHub Repository](https://github.com/Contargo/iris) for pull requests doing the same, so you don't have to put effort in something that's already done. +* If there is no active pull request implementing your feature then fork [IRIS](https://github.com/Contargo/iris) +* Create a new branch: +``` + git checkout -b my-branch master +``` +* Make your changes. +* Write Unit-Tests for all your changes. +* Run the full test suite to verify you did not broke anything: +``` + mvn clean verify +``` +* Commit your changes. Write a commit message that explains what your changes are for, so everyone can understand what it does. See [this](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) explanation for how to right commit message. If your changes are related to an open issue, reference the issue number in the last line of your commit message. Example: ```References #12```. +* Push the branch with your changes to GitHub. ```git push origin my-branch``` +* In GitHub, send a pull request to ```iris:master``` + +If you want to learn more about, how to write a good pull request, read [this](https://github.com/blog/1943-how-to-write-the-perfect-pull-request) blog post of GitHub. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..dba13ed2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..7e61f5c7 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +IRIS - Intermodal Routing Information System +========= + +The goal of IRIS is to create a centralized and transparent database for distance calculations in short-haul goods transport. +IRIS aims to provide such a platform, for both clients and service providers. + +IRIS is used to calculate truckings for transports from a hinterland terminal to the place of loading and the other way around. +The place of loading can be set very detailed by adjusting its coordinates. +IRIS uses these coordinates to calculate the truck route, distance, toll kilometers and the duration of the trucking. +An extension for barge and rail routings is already planned. + +Example: A freight container arrives in Mannheim via cargo ship and needs to be transported to 48 Berliner Str., Heidelberg, Germany. +After arriving at the Contargo terminal in Mannheim it needs to be loaded on a truck to be transported to its destination. +There, the recipient unloads the content and the truck transports the empty container back to the terminal. + +# Features +* Management of seaports, terminals and connections between them (GUI) +* Automatic creation of truck and barge routes between seaports, terminals and destinations (GUI & REST-API) +* Provision of detailed information for each route like distance, toll distance, duration, CO2 output (REST-API) +* Support for different container sizes and states (REST-API) +* Open Street Map Address resolution for destination points (GUI & REST-API) +* Management and resolution of custom static addresses as destination points (GUI & REST-API) +* Possibility to implement any authentication mechanism via Spring Security + +Further documentation about the IRIS-terminology can be found here: [terminology](docs/terminology.md). + + +#Prerequisites + - MySQL/MariaDb 5.5 or higher + - Maven 3 or higher + - JDK 8 or higher + + +#Getting started + +Simply clone this repository +```sh +$ git clone https://github.com/Contargo/iris.git +``` + +## Configuration + +Configuration is located in ```src/main/resources/``` + +The "general" configuration file is ```src/main/resources/application.properties``` + + +### Environment Properties + +Environment (System) specific configurations go to ```src/main/resources/application-.properties``` + +The environment-specific (e.g. ```application-dev.properties```) file overrides and adds to properties defined in the "general" properties-file (```application.properties```). Not overridden properties of ```application.properties``` remain valid. + +Default Environment is "dev" (so ```application-dev.properties``` is loaded by default). Environments can be set using System-Property "environment" + +```sh +mvn jetty:run -Denvironment=myenv # -> leads to use application-myenv.properties +``` +or environment-parameter environment: +```sh +export environment=superdev +mvn jetty:run # -> leads to use application-superdev.properties +``` + +#### Database + +All database connection settings are configured in ```application-.properties```. Adapt the corresponding properties to match your MySQL/MariaDb database connection settings. + +All needed database tables are created on application start using [Liquibase](http://www.liquibase.org/). + + +### Roles + +There are two different roles defined in IRIS: + +- ROLE_ADMIN + - can do anything +- ROLE_USER + - no GUI access + - limited REST Api access + + +### User Credentials + +User Credentials are are located in ```src/main/resources/usercredentials-.properties```. + +For development IRIS ships with two predefined users: +- admin@example.com with password admin: ROLE_ADMIN +- user@example.com with password user: ROLE_USER + +These are located in ```src/main/resources/usercredentials-dev.properties```. + +To add own user credentials do the following: +```sh +$ echo -n "yourpassword" | sha256sum +e3c652f0ba0b4801205814f8b6bc49672c4c74e25b497770bb89b22cdeb4e951 - +``` +Edit the file ```src/main/resources/usercredentials-.properties``` and add your user like this: +``` +youruser@example.com=e3c652f0ba0b4801205814f8b6bc49672c4c74e25b497770bb89b22cdeb4e951,ROLE_USER,enabled +``` + +You can specify login, password, role and set the enabled flag. + + +## Application Start + +In order to build the application you need Maven 3 and Oracle JDK 7. You also need your MySQL/MariaDb database set up as described above. You can then start the local web server: +```sh +$ mvn jetty:run +``` +You can run a full build including all tests with +```sh +$ mvn clean install +``` +Finally, point your browser to the url http://localhost:8082/. IRIS has both a basic user interface and a JSON API. Documentation for the API is located at http://localhost:8082/api/docs.html. + + +# Contributing + +If you want to contribute to IRIS, see our [contribution guidelines](CONTRIBUTING.md). + + +# Licensing + +IRIS is licensed under the GNU Affero General Public License, Version 3. See [LICENCE](LICENSE) for the full license text. diff --git a/docs/terminology.md b/docs/terminology.md new file mode 100644 index 00000000..58f45e21 --- /dev/null +++ b/docs/terminology.md @@ -0,0 +1,50 @@ +#Terminology + +##Terminal + +*inland (hinterland) container terminal* + +A facility where cargo containers are handled between different transport vehicles for onward transportation. +The handling of containers is typically between barges or cargo trains and land vehicles (trucks). + +* **Region:** An area defined by specific parameters. +Usually geographical division. (*Oberrhein, Mittelrhein, Niederrhein*) / (*Rhein-Neckar, Rhein-Main*). +Contargo uses three regions among other things to assign and store the CO₂-parameter of our barge fleet. + + +##Seaport +Location on a coast where sea vessels and barges can dock and transfer cargo from or to land. +Container seaports handle cargo in containers by different mechanical means. (crane, AGV, reach stacker) + + +##Connections +This shows the possibilities to connect a seaport and an inland terminal. +Different and multiple connections are possible. For example barge or rail or both. + +* **Diesel-km:** The kilometers traveled on the train route or the barge route by use of diesel fuel. +* **Electrical-km:** The kilometers on the train route traveled by use of electricity only. + + +##Static Addresses +A static address is a city with its corresponding postal code and country. For example *68159 Mannheim, Germany*. + + +##Cloud distance +A previously defined area (radius) around a static address. + + +##Route types + +* **Barge:** Transport of goods / cargo on barge. +Barge transport only happens between seaports and inland terminals or between inland terminals that are connected by a major river or canals. +* **Rail:** Transport of goods / cargo between seaports and inland terminals on a freight train using rail roads. +* **Truck:** Transport of goods / cargo from and to seaports and inland terminals and the loading / unloading site. + + +##Route Combination + +* **Waterway:** Transport per barge between seaport and inland terminal and additional transport per truck to the loading / unloading site. +* **Railway:** Transport per rail between seaport and inland terminal and additional transport per truck to the loading / unloading site. +* **Direct Truck:** Transport only per truck from or to the seaport and the loading site. +* **Roundtrip:** Waterway, Railway or Direct Truck transport from seaport to loading / unloading site and back to seaports. +* **All:** A list of all the possibilities for transport for a given loading site. \ No newline at end of file diff --git a/iris-api-tests/pom.xml b/iris-api-tests/pom.xml new file mode 100644 index 00000000..283a935b --- /dev/null +++ b/iris-api-tests/pom.xml @@ -0,0 +1,118 @@ + + + +4.0.0 + +net.contargo +iris-api-tests +1.0-SNAPSHOT + +jar + +IRIS API Tests + + + UTF-8 + UTF-8 + + 0.7-groovy-2.0 + 2.2.2 + 1.5 + 2.0 + 4.11 + 2.3 + 1.0.6 + 0.7.1 + + + + + + org.codehaus.gmaven + gmaven-plugin + ${gmavenVersion} + + ${gmavenProviderSelection} + UTF-8 + + + + + + testCompile + + + + + + org.codehaus.groovy + groovy-all + ${groovyVersion} + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.10 + + true + + + + exec-specs + test + + test + + + + **/*Spec.java + + false + + + + + + + + + + junit + junit-dep + ${junitVersion} + + + org.codehaus.groovy + groovy-all + ${groovyVersion} + + + org.spockframework + spock-core + ${spockCore} + test + + + org.codehaus.groovy + groovy-all + + + junit + junit-dep + + + org.hamcrest + hamcrest-core + + + + + org.codehaus.groovy.modules.http-builder + http-builder + ${httpBuilderVersion} + + + \ No newline at end of file diff --git a/iris-api-tests/src/test/groovy/address/AddressByAddressDetailsSpec.groovy b/iris-api-tests/src/test/groovy/address/AddressByAddressDetailsSpec.groovy new file mode 100644 index 00000000..7974d934 --- /dev/null +++ b/iris-api-tests/src/test/groovy/address/AddressByAddressDetailsSpec.groovy @@ -0,0 +1,80 @@ +package address + +import spock.lang.Specification +import util.ClientFactory + +/** + * @author Arnold Franke - franke@synyx.de + */ +class AddressByAddressDetailsSpec extends Specification { + + def "request for addresses by details like street, city etc. "() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + and: "address details city, postal code and street" + def urlParams = [ + "city": "karlsruhe", + "postalcode": "76131", + "street": "hirtenweg", + "country": "DE" + ] + + when: "addresses by city, postal code and street are requested" + def response = client.get(path: "/api/geocodes", query: urlParams); + def listOfAddressLists = response.data.geoCodeResponse.addresses + def staticAddressesObject = listOfAddressLists[0] + def nominatimAddressesObject = listOfAddressLists[1] + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "the staticAddressesObject contains a parent address and a list of static addresses" + def parentAddressStatic = staticAddressesObject.parentAddress + def staticaddresses = staticAddressesObject.addresses + staticaddresses.size() >= 1 + + and: "the parent address object has certain attributes" + parentAddressStatic.size() == 10 + parentAddressStatic.keySet().containsAll("countryCode", "niceName", "displayName", "osmId", "placeId", "shortName", "address", "type", "longitude", "latitude") + parentAddressStatic.type == "ADDRESS" + + and: "a static address contains certain attributes" + def staticaddress = staticaddresses[0] + staticaddress.size() == 10 + staticaddress.keySet().containsAll("countryCode", "niceName", "displayName", "osmId", "placeId", "shortName", "address", "type", "longitude", "latitude") + + and: "the type of a static address is ADDRESS" + staticaddress.type == "ADDRESS" + + and: "a static address contains a address map with certain attributes" + def addressmapStatic = staticaddress.address + addressmapStatic.size() == 6 + addressmapStatic.keySet().containsAll("suburb", "static_id", "postcode", "country_code", "city", "hashkey") + + and: "the nominatimAddressesObject contains a parent address and a list of static addresses" + def parentAddressNominatim = nominatimAddressesObject.parentAddress + def nominatimaddresses = nominatimAddressesObject.addresses + nominatimaddresses.size() >= 1 + + and: "the parent address object has certain attributes" + parentAddressNominatim.size() == 10 + parentAddressNominatim.keySet().containsAll("countryCode", "niceName", "displayName", "osmId", "placeId", "shortName", "address", "type", "longitude", "latitude") + parentAddressNominatim.type == "ADDRESS" + + and: "a static address contains certain attributes" + def nominatimaddress = staticaddresses[0] + nominatimaddress.size() == 10 + nominatimaddress.keySet().containsAll("countryCode", "niceName", "displayName", "osmId", "placeId", "shortName", "address", "type", "longitude", "latitude") + + and: "the type of a static address is ADDRESS" + nominatimaddress.type == "ADDRESS" + + and: "a static address contains a address map with certain attributes" + def addressmapNominatim = staticaddress.address + addressmapNominatim.size() == 6 + addressmapNominatim.keySet().containsAll("suburb", "static_id", "postcode", "country_code", "city", "hashkey") + + } +} diff --git a/iris-api-tests/src/test/groovy/address/AddressByGeolocationSpec.groovy b/iris-api-tests/src/test/groovy/address/AddressByGeolocationSpec.groovy new file mode 100644 index 00000000..6b6ae8ed --- /dev/null +++ b/iris-api-tests/src/test/groovy/address/AddressByGeolocationSpec.groovy @@ -0,0 +1,39 @@ +package address + +import spock.lang.Specification +import util.ClientFactory + +class AddressByGeolocationSpec extends Specification { + + def "request for geolocation"() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + when: "geolocation with latitude 49.123 and longituide 8.12 is requested" + def response = client.get(path: "/api/reversegeocode/49.123:8.12/") + def address = response.data.reverseGeocodeResponse.address + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "the address should have certain attributes" + def addressAttributes = address.keySet() + addressAttributes.size() == 10 + addressAttributes.containsAll("niceName", "address", "placeId", "countryCode", "osmId", "longitude", "latitude", + "type", "shortName", "displayName"); + + and: "the address is of type ADDRESS" + address.type == "ADDRESS" + + and: "the address has the requested longitude and latitude" + address.latitude == 49.123 + address.longitude == 8.12 + + and: "the address's actual address has certain attributes" + address.address.keySet().size() == 7 + println address.address.keySet() + address.address.keySet().containsAll("country", "country_code", "farmyard", "county", "postcode", + "state", "village") + } +} diff --git a/iris-api-tests/src/test/groovy/address/AddressByOsmIdSpec.groovy b/iris-api-tests/src/test/groovy/address/AddressByOsmIdSpec.groovy new file mode 100644 index 00000000..f9ae4e77 --- /dev/null +++ b/iris-api-tests/src/test/groovy/address/AddressByOsmIdSpec.groovy @@ -0,0 +1,49 @@ +package address + +import spock.lang.Specification +import util.ClientFactory + +class AddressByOsmIdSpec extends Specification { + + def "request for addresses with osm id "() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + when: "address with osm id 90085697 is requested" + def response = client.get(path: "/api/osmaddresses/90085697") + def addresses = response.data.geoCodeResponse.addresses.addresses + def parentAddress = response.data.geoCodeResponse.addresses.parentAddress + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "there should be a parent address" + parentAddress.size() == 1 + + and: "that parent address should have certain attributes" + def parentAddressAttributes = parentAddress.get(0).keySet() + parentAddressAttributes.size() == 10 + parentAddressAttributes.containsAll("niceName", "address", "placeId", "countryCode", "osmId", "longitude", + "latitude", "type", "shortName", "displayName") + + and: "there should be exactly one address in the response" + def innerAddresses = addresses.get(0) + innerAddresses.size() == 1 + + and: "that address should have certain attributes" + def address = innerAddresses.get(0) + def addressAttributes = address.keySet() + addressAttributes.size() == 10 + addressAttributes.containsAll("address", "displayName", "countryCode", "osmId", "latitude", "placeId", + "niceName", "shortName", "type", "longitude"); + + and: "that address has the requested osmid" + address.osmId == 90085697 + + and: "that address's actual address has certain attributes" + address["address"].keySet().size() == 10 + address["address"].keySet().containsAll("country", "country_code", "road", "city", "state_district", + "postcode", "suburb", "house_number", "address29", "state") + } +} \ No newline at end of file diff --git a/iris-api-tests/src/test/groovy/address/AddressWherePlaceIsInSpec.groovy b/iris-api-tests/src/test/groovy/address/AddressWherePlaceIsInSpec.groovy new file mode 100644 index 00000000..5aa1721b --- /dev/null +++ b/iris-api-tests/src/test/groovy/address/AddressWherePlaceIsInSpec.groovy @@ -0,0 +1,34 @@ +package address + +import spock.lang.Specification +import util.ClientFactory + +class AddressWherePlaceIsInSpec extends Specification { + + def "request for addresses containing place"() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + when: "addresses with place id 50350574 are requested" + def response = client.get(path: "/api/places/50350574/addresses") + def addresses = response.data.addresses + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "there should be fifteen addresses in the response" + addresses.size() == 15 + + and: "an address should have certain attributes" + def address = addresses.get(0) + def addressAttributes = address.keySet() + addressAttributes.size() == 10 + addressAttributes.containsAll("niceName", "address", "placeId", "countryCode", "osmId", "longitude", "latitude", + "type", "shortName", "displayName"); + + and: "that address has the requested placeid" + address.placeId == 50350574 + + } +} \ No newline at end of file diff --git a/iris-api-tests/src/test/groovy/address/staticsearch/StaticAddressByBoundingBoxSpec.groovy b/iris-api-tests/src/test/groovy/address/staticsearch/StaticAddressByBoundingBoxSpec.groovy new file mode 100644 index 00000000..b1e6aa0a --- /dev/null +++ b/iris-api-tests/src/test/groovy/address/staticsearch/StaticAddressByBoundingBoxSpec.groovy @@ -0,0 +1,34 @@ +package address.staticsearch + +import spock.lang.Specification +import util.ClientFactory + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +class StaticAddressByBoundingBoxSpec extends Specification{ + + def "request for Static Addresses matching the given bounding box"() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + and: "a bounding box definition" + def urlParams = [ + "lat": "49.0126538640", + "lon": "8.3770751953", + "distance": "10" + ] + + when: "static addresses are requested" + def response = client.get(path: "/api/staticaddresses", query: urlParams) + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "response consists of a list of static address uids" + def uidList = response.data.uids + uidList.size() == 1 + + } +} \ No newline at end of file diff --git a/iris-api-tests/src/test/groovy/address/staticsearch/StaticAddressByGeolocationSpec.groovy b/iris-api-tests/src/test/groovy/address/staticsearch/StaticAddressByGeolocationSpec.groovy new file mode 100644 index 00000000..6d89c634 --- /dev/null +++ b/iris-api-tests/src/test/groovy/address/staticsearch/StaticAddressByGeolocationSpec.groovy @@ -0,0 +1,57 @@ +package address.staticsearch + +import spock.lang.Specification +import util.ClientFactory + +/** + * @author Arnold Franke - franke@synyx.de + */ +class StaticAddressByGeolocationSpec extends Specification{ + + def "request for Static Addresses matching the given coordinates"() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + and: "a geolocation (latitude, longitude)" + def urlParams = [ + "lat": "49.0126538640", + "lon": "8.3770751953" + ] + + when: "static addresses are requested" + def response = client.get(path: "/api/staticaddresses", query: urlParams) + print(response.data) + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "response consists of a list of one addresslist object" + def addressListList = response.data.geoCodeResponse.addresses + addressListList.size() == 1 + + and: "the addresslistobject contains a parent address and a list of static addresses" + def addressListObject = addressListList[0] + def parentAddress = addressListObject.parentAddress + def staticaddresses = addressListObject.addresses + staticaddresses.size() == 1 + + and: "the parent address object has certain attributes" + parentAddress.size() == 10 + parentAddress.keySet().containsAll("countryCode", "niceName", "displayName", "osmId", "placeId", "shortName", "address", "type", "longitude", "latitude") + parentAddress.type == "ADDRESS" + + and: "a static address contains certain attributes" + def staticaddress = staticaddresses[0] + staticaddress.size() == 10 + staticaddress.keySet().containsAll("countryCode", "niceName", "displayName", "osmId", "placeId", "shortName", "address", "type", "longitude", "latitude") + + and: "the type of a static address is ADDRESS" + staticaddress.type == "ADDRESS" + + and: "a static address contains a address map with certain attributes" + def addressmap = staticaddress.address + addressmap.size() == 6 + addressmap.keySet().containsAll("suburb", "static_id", "postcode", "country_code", "city", "hashkey") + } +} \ No newline at end of file diff --git a/iris-api-tests/src/test/groovy/address/staticsearch/StaticAddressByPostalCodeAndCityAndCountrySpec.groovy b/iris-api-tests/src/test/groovy/address/staticsearch/StaticAddressByPostalCodeAndCityAndCountrySpec.groovy new file mode 100644 index 00000000..4a1f3a7d --- /dev/null +++ b/iris-api-tests/src/test/groovy/address/staticsearch/StaticAddressByPostalCodeAndCityAndCountrySpec.groovy @@ -0,0 +1,47 @@ +package address.staticsearch + +import spock.lang.Specification +import util.ClientFactory + +/** + * @author Arnold Franke - franke@synyx.de + */ +class StaticAddressByPostalCodeAndCityAndCountrySpec extends Specification { + + def "request for Static Addresses matching the given details"() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + and: "address details (postalCode, city, country)" + def urlParams = [ + "postalCode": "76131", + "city": "Karlsruhe", + "country": "DE" + ] + + when: "static addresses are requested" + def response = client.get(path: "/api/staticaddresses", query: urlParams) + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "response consists of one address" + def staticAddresses = response.responseData + staticAddresses.size() == 1 + + and: "a static address contains certain attributes" + def staticaddress = staticAddresses[0] + staticaddress.keySet().size() == 10 + staticaddress.keySet().containsAll("countryCode", "niceName", "displayName", "osmId", "placeId", "shortName", "address", "type", "longitude", "latitude") + + and: "a static address contains a address map with certain attributes" + def addressmap = staticaddress.address + addressmap.size() == 6 + addressmap.keySet().containsAll("suburb", "static_id", "postcode", "country_code", "city", "hashkey") + + and: "the type of a static address is ADDRESS" + staticaddress.type == "ADDRESS" + + } +} diff --git a/iris-api-tests/src/test/groovy/authorization/AuthorizationSpec.groovy b/iris-api-tests/src/test/groovy/authorization/AuthorizationSpec.groovy new file mode 100644 index 00000000..337c3aaf --- /dev/null +++ b/iris-api-tests/src/test/groovy/authorization/AuthorizationSpec.groovy @@ -0,0 +1,71 @@ +package authorization + +import groovyx.net.http.ContentType +import groovyx.net.http.HttpResponseException +import spock.lang.Specification +import util.ClientFactory + +class AuthorizationSpec extends Specification { + + def "request for api from non-authorized user"() { + + given: "a REST client without login credentials" + def client = ClientFactory.newUnauthorizedClient() + + when: "api is requested" + client.get(path: "/api/") + + then: "the request fails" + def e = thrown(HttpResponseException) + def response = e.getResponse(); + + then: "response status code should be 401 (Unauthorized)" + response.status == 401 + + and: "response should have correct authentication header" + response.getFirstHeader("WWW-Authenticate").getValue() == "Basic realm=\"IRIS API\"" + } + + def "request for seaport synchronisation for non allowed user"() { + + given: "a REST client with user login credentials" + def client = ClientFactory.newUserClient() + + when: "seaport creation is requested" + def seaportUid = System.nanoTime() + def content = [ + name: "seaport-${seaportUid}".toString(), + longitude: "8.${seaportUid}".toString(), + latitude: "49.${seaportUid}".toString() + ] + client.put(path: "/api/seaports/$seaportUid", body: content, contentType: ContentType.JSON) + + then: "the request fails" + def e = thrown(HttpResponseException) + + then: "response status code should be 403 (Forbidden)" + e.getResponse().status == 403 + } + + def "request for terminal synchronisation for non allowed user"() { + + given: "a REST client with user login credentials" + def client = ClientFactory.newUserClient() + + when: "terminal creation is requested" + def terminalUid = System.nanoTime() + def content = [ + name: "terminal-${terminalUid}".toString(), + longitude: "8.${terminalUid}".toString(), + latitude: "49.${terminalUid}".toString(), + region: "NOT_SET" + ] + client.put(path: "/api/terminals/$terminalUid", body: content, contentType: ContentType.JSON) + + then: "the request fails" + def e = thrown(HttpResponseException) + + then: "response status code should be 403 (Forbidden)" + e.getResponse().status == 403 + } +} diff --git a/iris-api-tests/src/test/groovy/connection/ConnectionSpec.groovy b/iris-api-tests/src/test/groovy/connection/ConnectionSpec.groovy new file mode 100644 index 00000000..8c315afa --- /dev/null +++ b/iris-api-tests/src/test/groovy/connection/ConnectionSpec.groovy @@ -0,0 +1,25 @@ +package connection + +import spock.lang.Specification +import util.ClientFactory + +class ConnectionSpec extends Specification { + + def "request for connections"() { + + given: "a REST client" + def client = new ClientFactory().newAdminClient() + + when: "connections for a specified terminal are requested" + def response = client.get(path: '/api/connections', query: [terminalUid: '1301000000000001']) + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "response body is as expected" + response.data[0].keySet() == ['routeType', 'terminalUid', 'seaportUid'] as Set + response.data[0].terminalUid == '1301000000000001' + response.data[0].seaportUid == '1301000000000002' + response.data[0].routeType == 'BARGE' + } +} \ No newline at end of file diff --git a/iris-api-tests/src/test/groovy/connection/ConnectionsForTerminalSpec.groovy b/iris-api-tests/src/test/groovy/connection/ConnectionsForTerminalSpec.groovy new file mode 100644 index 00000000..d7641b53 --- /dev/null +++ b/iris-api-tests/src/test/groovy/connection/ConnectionsForTerminalSpec.groovy @@ -0,0 +1,36 @@ +package connection + +import spock.lang.Specification +import util.ClientFactory + +class ConnectionsForTerminalSpec extends Specification { + + def "request for Connections belonging to specific Terminal"() { + given: "a REST client" + def client = ClientFactory.newAdminClient() + + and: "a Terminal Unique ID" + def urlParams = ["terminalUid": 1301000000000001] + + when: "connections are requested" + def response = client.get(path: "/api/connections", query: urlParams) + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "response consists at least one connection" + response.responseData.size() >= 1 + + and: "a connection contains certain attributes" + def connection = response.responseData[0] + connection.keySet().size() == 3 + connection.keySet().containsAll("seaportUid", "terminalUid", "routeType") + connection.terminalUid == "1301000000000001" + + and: "the attributes have certain types" + connection.seaportUid.isNumber() + connection.terminalUid.isNumber() + !connection.routeType.isNumber() + + } +} diff --git a/iris-api-tests/src/test/groovy/connection/EnrichedRouteSpec.groovy b/iris-api-tests/src/test/groovy/connection/EnrichedRouteSpec.groovy new file mode 100644 index 00000000..6340ac96 --- /dev/null +++ b/iris-api-tests/src/test/groovy/connection/EnrichedRouteSpec.groovy @@ -0,0 +1,64 @@ +package connection + +import spock.lang.Specification +import util.ClientFactory + +class EnrichedRouteSpec extends Specification { + + def "request for enriched route"() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + and: "a route specification" + def routeParams = [ + "data.parts[0].origin.longitude": 8.544177, + "data.parts[0].origin.latitude": 50.08162, + "data.parts[0].destination.longitude": 8.4232, + "data.parts[0].destination.latitude": 49.01179, + "data.parts[0].routeType": "TRUCK", + "data.parts[0].containerType": "FORTY", + "data.parts[0].containerState": "EMPTY", + "data.parts[1].origin.longitude": 8.4232, + "data.parts[1].origin.latitude": 49.01179, + "data.parts[1].destination.longitude": 8.544177, + "data.parts[1].destination.latitude": 50.08162, + "data.parts[1].routeType": "TRUCK", + "data.parts[1].containerType": "FORTY", + "data.parts[1].containerState": "FULL", + "data.parts[2].origin.longitude": 8.544177, + "data.parts[2].origin.latitude": 50.08162, + "data.parts[2].destination.longitude": 4.3, + "data.parts[2].destination.latitude": 51.36833, + "data.parts[2].routeType": "BARGE", + "data.parts[2].containerType": "FORTY", + "data.parts[2].containerState": "FULL", + ] + + when: "details are requested" + def response = client.get(path: "/api/routedetails", query: routeParams) + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "the route has certain attributes" + def route = response.data.response.route + route.keySet().size() == 9 + route.keySet().containsAll("product", "errors", "responsibleTerminal", "direction", "name", "roundTrip", "data", "shortName", "url") + + and: "the route's data has certain attribures" + route.data.keySet().size() == 8 + route.data.keySet().containsAll("co2", "parts", "totalRealTollDistance", "totalDuration", "totalDistance", + "totalTollDistance", "co2DirectTruck", "totalOnewayTruckDistance") + + and: "each route part has certain attributes" + route.data.parts.each { + assert it.keySet().size() == 8 + assert it.keySet().containsAll("containerType", "routeType", "direction", "name", "containerState", "origin", + "data", "destination") + assert it.data.keySet().size() == 7 + assert it.data.keySet().containsAll("electricDistance", "duration", "distance", "dieselDistance", + "tollDistance", "airlineDistance", "co2") + } + } +} \ No newline at end of file diff --git a/iris-api-tests/src/test/groovy/connection/SeaportRouteSpec.groovy b/iris-api-tests/src/test/groovy/connection/SeaportRouteSpec.groovy new file mode 100644 index 00000000..537e338f --- /dev/null +++ b/iris-api-tests/src/test/groovy/connection/SeaportRouteSpec.groovy @@ -0,0 +1,45 @@ +package connection + +import spock.lang.Specification +import util.ClientFactory + +class SeaportRouteSpec extends Specification { + + def "request for seaport routes"() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + when: "routes for a specified seaport are requested" + def requestUrl = String.format('/api/connections/%s/%s:%s/%s', 1301000000000002, 49.01179, 8.4232, false) + def params = [containerType: "TWENTY_LIGHT", isImport: false, combo: "WATERWAY"] + def response = client.get(path: requestUrl, query: params) + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "there should be at least one route" + def routes = response.data.response.routes + !routes.isEmpty() + + routes.each { + and: "each route has certain fields" + def routeAttributes = it.keySet() + assert routeAttributes.size() == 9 + assert routeAttributes.containsAll("roundTrip", "product", "shortName", "name", "data", "errors", "direction", "url", "responsibleTerminal") + + and: "each route's data field has certain fields" + def data = it.data + def dataAttributes = data.keySet() + assert dataAttributes.size() == 8 + assert dataAttributes.containsAll("co2", "co2DirectTruck", "totalDistance", "totalOnewayTruckDistance", + "totalRealTollDistance", "totalTollDistance", "totalDuration", "parts") + + and: "the route parts have certain fields" + def partAttributes = data.parts.first().keySet() + assert partAttributes.size() == 8 + assert partAttributes.containsAll("name", "data", "origin", "containerState", "containerType", "direction", + "routeType", "destination") + } + } +} \ No newline at end of file diff --git a/iris-api-tests/src/test/groovy/connection/SeaportsInConnectionsSpec.groovy b/iris-api-tests/src/test/groovy/connection/SeaportsInConnectionsSpec.groovy new file mode 100644 index 00000000..bc4066c0 --- /dev/null +++ b/iris-api-tests/src/test/groovy/connection/SeaportsInConnectionsSpec.groovy @@ -0,0 +1,49 @@ +package connection + +import spock.lang.* +import util.ClientFactory +import org.apache.http.client.HttpResponseException + +/** + * @author Oliver Messner - messner@synyx.de + */ +class SeaportsInConnectionsSpec extends Specification { + + def "request for connected seaport"() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + when: "request is sent to server" + def response = client.get(path: "/api/connections/seaports") + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "the seaport object has certain attributes" + def seaports = response.data.seaports + !seaports.isEmpty() + seaports.each { + assert it.keySet().size() == 6 + assert it.keySet().containsAll("latitude", "longitude", "name", "enabled", "type", "uniqueId") + } + + and: "links are provided" + def links = response.data.links + !links.isEmpty() + } + + def "request for undefined combo type"() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + when: "an invalid combotype is requested" + client.get(path: "/api/connections/seaports", query: [combo: 'INVALID COMBO TYPE']) + + then: "response status type is bad request" + def e = thrown(HttpResponseException) + e.message == 'Bad Request' + e.statusCode == 400 + } +} \ No newline at end of file diff --git a/iris-api-tests/src/test/groovy/connection/TerminalsConnectedToSeaportSpec.groovy b/iris-api-tests/src/test/groovy/connection/TerminalsConnectedToSeaportSpec.groovy new file mode 100644 index 00000000..7b689dea --- /dev/null +++ b/iris-api-tests/src/test/groovy/connection/TerminalsConnectedToSeaportSpec.groovy @@ -0,0 +1,34 @@ +package connection + +import spock.lang.Specification +import util.ClientFactory + +class TerminalsConnectedToSeaportSpec extends Specification { + + def "request for terminals connected to a seaport with route type BARGE"() { + given: "a REST client" + def client = ClientFactory.newAdminClient() + + and: "a seaport unique ID and a route type" + def urlParams = [ + "seaportUid": 1301000000000002, + "routeType": "BARGE" + ] + + when: "terminals for this combination are requested" + def response = client.get(path: "/api/terminals", query: urlParams) + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "the response contains terminals" + def terminals = response.data.response.terminals + !terminals.isEmpty() + + and: "the terminals contains certain attributes" + terminals.each { + assert it.keySet().size() == 7 + assert it.keySet().containsAll("latitude", "longitude", "name", "enabled", "uniqueId", "type", "region") + } + } +} diff --git a/iris-api-tests/src/test/groovy/countries/CountriesSpec.groovy b/iris-api-tests/src/test/groovy/countries/CountriesSpec.groovy new file mode 100644 index 00000000..f4fe84f6 --- /dev/null +++ b/iris-api-tests/src/test/groovy/countries/CountriesSpec.groovy @@ -0,0 +1,30 @@ +package countries + +import spock.lang.Specification +import util.ClientFactory + +class CountriesSpec extends Specification { + + def "request for countries"() { + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + when: "countries are requested" + def response = client.get(path: "/api/countries/") + def countries = response.data.countriesResponse.countries + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "countries amount should be 12" + countries.size() == 12 + + and: "Belgium should be in the list" + countries.contains([name: 'Belgium', value: 'BE']); + + and: "Germany should be in the list" + countries.contains([name: 'Germany', value: 'DE']); + } + +} diff --git a/iris-api-tests/src/test/groovy/distance/AirlineDistanceSpec.groovy b/iris-api-tests/src/test/groovy/distance/AirlineDistanceSpec.groovy new file mode 100644 index 00000000..d1e16eb0 --- /dev/null +++ b/iris-api-tests/src/test/groovy/distance/AirlineDistanceSpec.groovy @@ -0,0 +1,35 @@ +package distance + +import spock.lang.Specification +import util.ClientFactory + +class AirlineDistanceSpec extends Specification { + + def "request for airline distance between karlsruhe and duisburg"() { + + def latKA = 49.008085 + def lonKA = 8.403756 + + def latDU = 51.435146 + def lonDU = 6.762691 + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + when: "airline distance is requested" + def params = [alat:latKA, alon:lonKA, blat:latDU, blon:lonDU] + def response = client.get(path: "/api/airlineDistance", query: params) + def airlineDistance = response.data.airlineDistance + + then: "response status code should be 200 (OK)" + response.status == 200 + + and: "airlineDistance should consist of unit and distance and links" + airlineDistance.keySet().size() == 3 + airlineDistance.keySet().containsAll("unit", "distance", "links") + + and: "unit is meter" + airlineDistance.unit == "meter" + + } +} diff --git a/iris-api-tests/src/test/groovy/distance/CloudDistanceSpec.groovy b/iris-api-tests/src/test/groovy/distance/CloudDistanceSpec.groovy new file mode 100644 index 00000000..d2e82aa7 --- /dev/null +++ b/iris-api-tests/src/test/groovy/distance/CloudDistanceSpec.groovy @@ -0,0 +1,38 @@ +package distance + +import spock.lang.Specification +import util.ClientFactory + +class CloudDistanceSpec extends Specification { + + def "request for cloud distance between karlsruhe and terminal one"() { + + def idKarlsruhe = 1301000000000001 + def idFrankfurt = 1301000000000001 + + given: "a REST client" + def client = ClientFactory.newAdminClient() + + when: "distance is requested" + def params = [ + terminal: idFrankfurt, + address: idKarlsruhe + ] + def response = client.get(path: "/api/distancecloudaddress", query: params) + + then: "response status code should be 200 (OK)" + response.status == 200 + + def responseData = response.data + + and: "responseData should consist of address and links" + responseData.keySet().size() == 2 + responseData.keySet().containsAll("address", "links") + + and: "address should consist of certain attributes" + def address = responseData.address + address.keySet().size() == 10 + address.keySet().containsAll("country", "hashKey", "distance", "city", "postalcode", "airLineDistanceMeter", + "errorMessage", "suburb", "tollDistance", "uniqueId") + } +} diff --git a/iris-api-tests/src/test/groovy/seaport/SeaportSpec.groovy b/iris-api-tests/src/test/groovy/seaport/SeaportSpec.groovy new file mode 100644 index 00000000..8d8a79ef --- /dev/null +++ b/iris-api-tests/src/test/groovy/seaport/SeaportSpec.groovy @@ -0,0 +1,49 @@ +package seaport + +import groovyx.net.http.ContentType +import spock.lang.Specification +import util.ClientFactory + +class SeaportSpec extends Specification { + + def 'create new seaport'() { + + given: "a REST client" + def client = new ClientFactory().newAdminClient() + + when: 'new seaport is created' + def seaportUid = System.nanoTime() + def content = [ + name: "seaport-${seaportUid}".toString(), + longitude: "8.${seaportUid}".toString(), + latitude: "49.${seaportUid}".toString() + ] + + def response = client.put(path: "/api/seaports/$seaportUid", body: content, contentType: ContentType.JSON) + + then: 'response status code should be 201 (CREATED)' + response.status == 201 + } + + def 'create and update seaport'() { + + given: "a REST client" + def client = new ClientFactory().newAdminClient() + + and: 'a new seaport' + def seaportUid = System.nanoTime() + def content = [ + name: "seaport-${seaportUid}".toString(), + longitude: "8.${seaportUid}".toString(), + latitude: "49.${seaportUid}".toString() + ] + + client.put(path: "/api/seaports/$seaportUid", body: content, contentType: ContentType.JSON) + + when: 'seaport is updated' + def response = client.put(path: "/api/seaports/$seaportUid", body: content, contentType: ContentType.JSON) + + then: 'response status code should be 204 (NO CONTENT)' + response.status == 204 + } +} diff --git a/iris-api-tests/src/test/groovy/terminal/TerminalSpec.groovy b/iris-api-tests/src/test/groovy/terminal/TerminalSpec.groovy new file mode 100644 index 00000000..6ec5c115 --- /dev/null +++ b/iris-api-tests/src/test/groovy/terminal/TerminalSpec.groovy @@ -0,0 +1,51 @@ +package terminal + +import groovyx.net.http.ContentType +import spock.lang.Specification +import util.ClientFactory + +class TerminalSpec extends Specification { + + def 'create new terminal'() { + + given: "a REST client" + def client = new ClientFactory().newAdminClient() + + when: 'new terminal is created' + def terminalUid = System.nanoTime() + def content = [ + name: "terminal-${terminalUid}".toString(), + longitude: "8.${terminalUid}".toString(), + latitude: "49.${terminalUid}".toString(), + region: "NOT_SET" + ] + + def response = client.put(path: "/api/terminals/$terminalUid", body: content, contentType: ContentType.JSON) + + then: 'response status code should be 201 (CREATED)' + response.status == 201 + } + + def 'create and update terminal'() { + + given: "a REST client" + def client = new ClientFactory().newAdminClient() + + and: 'a new terminal' + def terminalUid = System.nanoTime() + def content = [ + name: "terminal-${terminalUid}".toString(), + longitude: "8.${terminalUid}".toString(), + latitude: "49.${terminalUid}".toString(), + region: "NOT_SET" + ] + + client.put(path: "/api/terminals/$terminalUid", body: content, contentType: ContentType.JSON) + + when: 'terminal is updated' + def response = client.put(path: "/api/terminals/$terminalUid", body: content, contentType: ContentType.JSON) + + then: 'response status code should be 204 (NO CONTENT)' + assert response.status == 204 + } +} diff --git a/iris-api-tests/src/test/groovy/util/ClientFactory.groovy b/iris-api-tests/src/test/groovy/util/ClientFactory.groovy new file mode 100644 index 00000000..a52a553e --- /dev/null +++ b/iris-api-tests/src/test/groovy/util/ClientFactory.groovy @@ -0,0 +1,26 @@ +package util + +import groovyx.net.http.RESTClient + +class ClientFactory { + + static final ENDPOINT = System.getProperty("endpoint") == null ? "http://localhost:8082" : System.getProperty("endpoint") + + static def newAdminClient() { + def client = newUnauthorizedClient() + + // prepare for preemptive authentication + client.headers['Authorization'] = 'Basic ' + "admin@example.com:admin".getBytes('UTF-8').encodeBase64() + return client + } + + static def newUserClient() { + def client = newUnauthorizedClient() + client.headers['Authorization'] = 'Basic ' + "user@example.com:user".getBytes('UTF-8').encodeBase64() + return client + } + + static def newUnauthorizedClient() { + return new RESTClient(ENDPOINT) + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..80e4605f --- /dev/null +++ b/pom.xml @@ -0,0 +1,816 @@ + + 4.0.0 + + net.contargo + iris + 1.6 + war + + ${project.groupId}:${project.artifactId} + + Intermodal Routing Information System + https://github.com/Contargo/iris + + + + GNU Affero General Public License, Version 3 + http://www.gnu.org/licenses/agpl-3.0.txt + + + + + Contargo GmbH & Co. KG + http://www.contargo.net + + + + + IRIS Development + iris.development@contargo.net + + + + + 1.8 + ${maven.build.timestamp} + + UTF-8 + UTF-8 + + 4.1.6.RELEASE + 3.2.7.RELEASE + 1.8.0.RELEASE + + 1.7.6 + + 8.1.14.v20131031 + + 1.1.2 + + jacoco + target/jacoco.exec + target/surefire-reports + reuseReports + target/jasmine/total-coverage.dat + + src/main/java + + + + https://github.com/Contargo/iris + scm:git:https://github.com/Contargo/iris.git + scm:git:https://github.com/Contargo/iris.git + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + nexus.synyx.org + Synyx OpenSource Repository + http://repo.synyx.org + + + mapfish + mapfish geotools + http://dev.mapfish.org/maven/repository/org/geotools// + + + osgeo + Open Source Geospatial Foundation Repository + http://download.osgeo.org/webdav/geotools/ + + + oss-jfrog-artifactory + oss-jfrog-artifactory-releases + http://oss.jfrog.org/artifactory/oss-release-local + + + + + + org.springframework + spring-core + ${org.springframework-version} + + + + commons-logging + commons-logging + + + + + + org.springframework + spring-context + ${org.springframework-version} + + + + commons-logging + commons-logging + + + + + + org.springframework + spring-context-support + ${org.springframework-version} + + + + org.springframework + spring-webmvc + ${org.springframework-version} + + + + org.springframework + spring-web + ${org.springframework-version} + + + + org.springframework + spring-orm + ${org.springframework-version} + + + + org.springframework.security + spring-security-web + ${org.springframework.security-version} + + + + + commons-logging + commons-logging + + + + + + org.springframework.security + spring-security-config + ${org.springframework.security-version} + + + + + commons-logging + commons-logging + + + + + + org.springframework.security + spring-security-taglibs + ${org.springframework.security-version} + + + + + org.springframework.data + spring-data-jpa + ${org.springframework.data.jpa-version} + + + + org.hibernate + hibernate-entitymanager + 4.3.9.Final + + + org.slf4j + slf4j-api + + + + + + org.hibernate + hibernate-validator + 4.3.2.Final + + + + mysql + mysql-connector-java + + 5.1.29 + + + + org.liquibase + liquibase-core + 3.3.2 + + + + commons-dbcp + commons-dbcp + 1.4 + + + + commons-lang + commons-lang + 2.6 + + + + org.geotools + gt-api + 2.7.4 + + + + com.fasterxml.jackson.core + jackson-databind + 2.5.2 + + + + joda-time + joda-time + 2.3 + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + javax.servlet.jsp + jsp-api + 2.2 + provided + + + javax.servlet + jstl + 1.2 + + + + + displaytag + displaytag + 1.2 + + + org.slf4j + slf4j-log4j12 + + + org.slf4j + jcl104-over-slf4j + + + + + + opensymphony + sitemesh + 2.4.2 + + + + org.synyx + spring-helper-sitemesh + 0.1 + + + commons-logging + commons-logging + + + + + + net.contargo + big-decimal-validator + 1.2.1 + + + + + + org.slf4j + slf4j-api + ${org.slf4j-version} + + + + org.slf4j + jcl-over-slf4j + ${org.slf4j-version} + + + + org.slf4j + jul-to-slf4j + ${org.slf4j-version} + + + + ch.qos.logback + logback-classic + ${logback.version} + + + ch.qos.logback + logback-core + ${logback.version} + + + ch.qos.logback + logback-access + ${logback.version} + + + org.logback-extensions + logback-ext-spring + 0.1.2 + + + + org.apache.httpcomponents + httpclient + 4.3.2 + + + commons-logging + commons-logging + + + + + + + junit + junit + 4.12 + test + + + org.hamcrest + hamcrest-core + + + + + + org.hamcrest + hamcrest-all + 1.3 + test + + + + org.mockito + mockito-all + 1.9.5 + test + + + + org.springframework + spring-test + ${org.springframework-version} + test + + + + org.unitils + unitils-core + 3.4.2 + + + commons-logging + commons-logging + + + + + + com.jayway.jsonpath + json-path + 0.8.1 + test + + + + com.jayway.jsonpath + json-path-assert + 0.9.1 + test + + + + org.springframework.hateoas + spring-hateoas + 0.9.0.RELEASE + + + org.slf4j + slf4j-api + + + + + + com.mangofactory + swagger-springmvc + 0.9.5 + + + + org.ajar + swagger-spring-mvc-ui + 0.4 + + + + net.sf.ehcache + ehcache + 2.9.0 + + + + com.google.guava + guava + 16.0.1 + + + + javax.interceptor + javax.interceptor-api + 1.2 + provided + + + + + ${srcDir} + + + src/main/resources + true + + **/*.xml + **/*.properties + **/*.json + + + + + + ../iris/src/test/resources + true + + **/*.xml + **/*.properties + **/*.json + + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.1 + + true + false + release + deploy + + + + + org.jacoco + jacoco-maven-plugin + 0.7.4.201502262128 + + + net.contargo.* + + ${project.basedir}/target/jacoco.exec + + + + + prepare-agent + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${java-version} + ${java-version} + + + + + org.apache.maven.plugins + maven-war-plugin + 2.4 + + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.3.1 + + + enforce-stuff + + enforce + + + + + + commons-logging + + true + + + [3.0.4,) + + + [1.6.0,) + + + true + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + true + + + + unittests + test + + test + + + + **/*UnitTest.java + + false + + + + integrationtests + integration-test + + test + + + + **/*IntegrationTest.java + **/*WebTest.java + + false + + + + + + + com.github.klieber + phantomjs-maven-plugin + 0.2 + + + + install + + + + + 1.9.2 + + + + + com.github.searls + jasmine-maven-plugin + 1.3.1.1 + + + + + test + + + + + src/main/webapp/client/js + + true + + org.openqa.selenium.phantomjs.PhantomJSDriver + + + ${phantomjs.binary} + + + + jasmine-jquery-1.3.1.js + globalsetup.js + **Test.js + models/**Test.js + views/**Test.js + + + lib/jquery*.js + lib/select2.js + lib/underscore.js + lib/backbone.js + lib/bootstrap.min.js + lib/bootstrap-notify.js + lib/handlebars-1.3.0.js + + + routing/SelectableAwareCollection.js + routing/models/*.js + routing/views/*.js + routing/*.js + + + + + + + + org.mortbay.jetty + jetty-maven-plugin + ${jetty.version} + + + + 8082 + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.1 + + -Xdoclint:none + + + + + IRIS + + + + + + release + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.2 + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + + jsSonar + + src/main/webapp/client/js/routing + js + js + + + + + + com.github.timurstrekalov + saga-maven-plugin + 1.4.0 + + + + coverage + + + + + http://localhost:${jasmine.serverPort} + ${project.build.directory}/saga-coverage + + .*/spec/.* + + + + + + + + com.google.code.maven-replacer-plugin + replacer + 1.5.2 + + + + replace + + + + + ${project.build.directory}/saga-coverage/total-coverage.dat + ${project.build.directory}/jasmine/total-coverage.dat + true + + + SF:http://localhost:\d+/src/routing/ + SF:${project.build.sourceDirectory}/ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/net/contargo/iris/BoundingBox.java b/src/main/java/net/contargo/iris/BoundingBox.java new file mode 100644 index 00000000..6c65e0a6 --- /dev/null +++ b/src/main/java/net/contargo/iris/BoundingBox.java @@ -0,0 +1,94 @@ +package net.contargo.iris; + +import java.math.BigDecimal; + + +/** + * @author Marc Kannegiesser - kannegiesser@synyx.de + */ +public class BoundingBox { + + private static final Double EARTH_RADIUS = 6371.01d; + private static final Double MIN_LAT = Math.toRadians(-90d); + private static final Double MIN_LON = Math.toRadians(90d); + private static final Double MAX_LON = Math.toRadians(-180); + private static final Double MAX_LAT = Math.toRadians(180d); + + private final GeoLocation lowerLeft; + private final GeoLocation upperRight; + + /** + * Calculates the Bounding-Box with the given geolocation as center and a "radius" of given distance in kilometers. + * + *
+     +-------------------------X   upperRight
+     |                         |
+     |            X            |
+     |                         |
+     X-------------------------+   lowerLeft
+
+     * 
+ * + * @param distanceInKm distance in km + * + * @return + */ + + public BoundingBox(GeoLocation location, Double distanceInKm) { + + Double angularDistance = distanceInKm / EARTH_RADIUS; + + double lat = fromDegree(location.getLatitude()); + double lon = fromDegree(location.getLongitude()); + + Double minLatitude = lat - angularDistance; + Double maxLatitude = lat + angularDistance; + + Double minLongitude; + Double maxLongitude; + + if (minLatitude > MIN_LAT && maxLatitude < MAX_LAT) { + Double deltaLongitude = Math.asin(Math.sin(angularDistance) / Math.cos(lat)); + + minLongitude = lon - deltaLongitude; + + if (minLongitude < MIN_LON) { + minLongitude += 2d * Math.PI; + } + + maxLongitude = lon + deltaLongitude; + + if (maxLongitude > MAX_LON) { + maxLongitude -= 2d * Math.PI; + } + } else { + minLatitude = Math.max(minLatitude, MIN_LAT); + + maxLatitude = Math.min(maxLatitude, MAX_LAT); + + minLongitude = MIN_LON; + + maxLongitude = MAX_LON; + } + + this.lowerLeft = new GeoLocation(Math.toDegrees(minLatitude), Math.toDegrees(minLongitude)); + this.upperRight = new GeoLocation(Math.toDegrees(maxLatitude), Math.toDegrees(maxLongitude)); + } + + private double fromDegree(BigDecimal value) { + + return Math.toRadians(value.doubleValue()); + } + + + public GeoLocation getLowerLeft() { + + return lowerLeft; + } + + + public GeoLocation getUpperRight() { + + return upperRight; + } +} diff --git a/src/main/java/net/contargo/iris/GeoLocation.java b/src/main/java/net/contargo/iris/GeoLocation.java new file mode 100644 index 00000000..128a43e6 --- /dev/null +++ b/src/main/java/net/contargo/iris/GeoLocation.java @@ -0,0 +1,197 @@ +package net.contargo.iris; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import net.contargo.iris.util.BigDecimalComparator; + +import org.apache.commons.lang.builder.HashCodeBuilder; + +import org.hibernate.validator.constraints.Range; + +import org.springframework.format.number.NumberFormatter; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import java.text.NumberFormat; + +import java.util.Locale; + +import javax.persistence.MappedSuperclass; + +import javax.validation.constraints.NotNull; + + +/** + * This class represents a Geolocation. + * + *

The equal method tests if the gps coordinates are the same.

+ * + * @author Sven Mueller - mueller@synyx.de + * @author Aljona Murygina - murygina@synyx.de + * @author Vincent Potucek - potucek@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@MappedSuperclass +public class GeoLocation { + + private static final int SCALE = 10; + private static final int MAX_VALUE_COORD = 180; + private static final int MIN_VALUE_COORD = -180; + + @JsonProperty("lat") + @NotNull + @Range(min = MIN_VALUE_COORD, max = MAX_VALUE_COORD) + private BigDecimal latitude; + + @JsonProperty("lon") + @NotNull + @Range(min = MIN_VALUE_COORD, max = MAX_VALUE_COORD) + private BigDecimal longitude; + + public GeoLocation() { + + // default constructor to call from subclasses + } + + + public GeoLocation(BigDecimal latitude, BigDecimal longitude) { + + this.latitude = latitude; + this.longitude = longitude; + } + + + public GeoLocation(double plat, double plon) { + + double lat = plat; + double lon = plon; + + while (lon > MAX_VALUE_COORD) { + lon = lon - MAX_VALUE_COORD; + } + + while (lat > MAX_VALUE_COORD) { + lat = lat - MAX_VALUE_COORD; + } + + while (lon < 0) { + lon = lon + MAX_VALUE_COORD; + } + + while (lat < 0) { + lat = lat + MAX_VALUE_COORD; + } + + this.latitude = new BigDecimal(lat); + this.longitude = new BigDecimal(lon); + } + + public BigDecimal getLatitude() { + + if (latitude == null) { + return null; + } + + return latitude.setScale(SCALE, RoundingMode.HALF_EVEN); + } + + + public void setLatitude(BigDecimal latitude) { + + this.latitude = latitude; + } + + + public BigDecimal getLongitude() { + + if (longitude == null) { + return null; + } + + return longitude.setScale(SCALE, RoundingMode.HALF_EVEN); + } + + + public void setLongitude(BigDecimal longitude) { + + this.longitude = longitude; + } + + + public String getNiceName() { + + NumberFormatter nf = new NumberFormatter("#0.0#####"); + NumberFormat numberFormat = nf.getNumberFormat(Locale.getDefault()); + + return numberFormat.format(getLatitude()) + ":" + numberFormat.format(getLongitude()); + } + + + @Override + public int hashCode() { + + HashCodeBuilder builder = new HashCodeBuilder(); + + if (latitude != null) { + builder.append(latitude.doubleValue()); + } + + if (longitude != null) { + builder.append(longitude.doubleValue()); + } + + return builder.toHashCode(); + } + + + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (!(obj instanceof GeoLocation)) { + return false; + } + + GeoLocation other = (GeoLocation) obj; + + BigDecimalComparator comparator = new BigDecimalComparator(); + + return !(comparator.compare(this.getLatitude(), other.getLatitude()) != 0 + || comparator.compare(this.getLongitude(), other.getLongitude()) != 0); + } + + + @Override + public String toString() { + + return "GeoLocationImpl [latitude=" + latitude + ", longitude=" + longitude + "]"; + } + + + /** + * Calculates the Bounding-Box with this as center and a "radius" of given distance in kilometers. + * + *

This returns two GeoLocations as BoundingBox that wraps the lower left and upper right of the box

+ * + *
+     +-------------------------X   result[1]
+     |                         |
+     |            X            |
+     |                         |
+     X-------------------------+   result[0]
+
+     * 
+ * + * @param distanceInKm distance in km + * + * @return BoundingBox of the given distanceInKm + */ + public BoundingBox getBoundingBox(Double distanceInKm) { + + return new BoundingBox(this, distanceInKm); + } +} diff --git a/src/main/java/net/contargo/iris/Message.java b/src/main/java/net/contargo/iris/Message.java new file mode 100644 index 00000000..b66430cd --- /dev/null +++ b/src/main/java/net/contargo/iris/Message.java @@ -0,0 +1,66 @@ +package net.contargo.iris; + +import org.apache.commons.lang.builder.ToStringBuilder; + + +/** + * Bean representing a Message. + * + * @author Marc Kannegiesser - kannegiesser@synyx.de + * @author Oliver Messner - messner@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +public final class Message { + + public static enum MessageType { + + SUCCESS, + ERROR, + WARNING + } + + private final String message; + private final MessageType type; + + public Message(String message, MessageType type) { + + this.message = message; + this.type = type; + } + + public MessageType getType() { + + return type; + } + + + public String getMessage() { + + return message; + } + + + public static Message error(String message) { + + return new Message(message, MessageType.ERROR); + } + + + public static Message success(String message) { + + return new Message(message, MessageType.SUCCESS); + } + + + public static Message warning(String message) { + + return new Message(message, MessageType.WARNING); + } + + + @Override + public String toString() { + + return new ToStringBuilder(this).append("type", type).append("message", message).toString(); + } +} diff --git a/src/main/java/net/contargo/iris/address/Address.java b/src/main/java/net/contargo/iris/address/Address.java new file mode 100644 index 00000000..60bd281b --- /dev/null +++ b/src/main/java/net/contargo/iris/address/Address.java @@ -0,0 +1,206 @@ +package net.contargo.iris.address; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import net.contargo.iris.GeoLocation; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; + +import java.util.HashMap; +import java.util.Map; + + +/** + * Represents an address, which is delivered by an address resolution provider (nominatim). + * + * @author Aljona Murygina - murygina@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Address extends GeoLocation { + + public static final String COUNTRY_CODE = "country_code"; + private static final String DISPLAY_NAME = "display_name"; + private static final String PLACE_ID = "place_id"; + private static final String OSM_ID = "osm_id"; + private static final String SHORT_NAME = "short_name"; + private static final String SUBURB = "suburb"; + private static final String CITY = "city"; + + @JsonProperty(DISPLAY_NAME) + private String displayName; + + @JsonProperty(OSM_ID) + private long osmId; + + @JsonProperty(PLACE_ID) + private long placeId; + + @JsonProperty(SHORT_NAME) + private String shortName; + + private Map address = new HashMap<>(); + + public Address(String displayName) { + + super(); + this.displayName = displayName; + } + + + public Address() { + + // needed in test. + } + + + public Address(BigDecimal latitude, BigDecimal longitude) { + + super(latitude, longitude); + } + + public String getDisplayName() { + + return displayName; + } + + + public void setDisplayName(String displayName) { + + this.displayName = displayName; + } + + + public String getShortName() { + + return shortName; + } + + + public void setShortName(String shortName) { + + this.shortName = shortName; + } + + + public Map getAddress() { + + return address; + } + + + public void setAddress(Map address) { + + this.address = address; + } + + + public long getOsmId() { + + return osmId; + } + + + public void setOsmId(long osmId) { + + this.osmId = osmId; + } + + + public long getPlaceId() { + + return placeId; + } + + + public void setPlaceId(long placeId) { + + this.placeId = placeId; + } + + + @Override + public boolean equals(Object o) { + + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + final Address oth = (Address) o; + + return new EqualsBuilder().append(displayName, oth.displayName).append(osmId, oth.osmId).append(placeId, + oth.placeId).append(shortName, oth.shortName).appendSuper(super.equals(o)).isEquals(); + } + + + @Override + public int hashCode() { + + return new HashCodeBuilder().append(displayName).append(osmId).append(placeId).append(shortName).appendSuper( + super.hashCode()).toHashCode(); + } + + + public String getCountryCode() { + + return this.address.get(COUNTRY_CODE); + } + + + @Override + public String getNiceName() { + + String addressText = getAddressText(); + + if (StringUtils.hasLength(addressText)) { + return addressText; + } + + String name = getShortName(); + + if (StringUtils.hasLength(name)) { + return name; + } + + return getDisplayName(); + } + + + private String getAddressText() { + + String postalCode = address.get("postcode"); + + if (postalCode == null) { + postalCode = address.get("boundary"); + } + + if (address.containsKey(CITY) && postalCode != null) { + if (StringUtils.hasText(address.get(SUBURB))) { + return String.format("%s %s (%s)", postalCode, address.get(CITY), address.get(SUBURB)); + } else { + return String.format("%s %s", postalCode, address.get(CITY)); + } + } + + return null; + } + + + @Override + public String toString() { + + return "Address [displayName=" + displayName + ", osmId=" + osmId + + ", placeId=" + placeId + ", shortName=" + shortName + + ", address=" + address + "]"; + } +} diff --git a/src/main/java/net/contargo/iris/address/AddressList.java b/src/main/java/net/contargo/iris/address/AddressList.java new file mode 100644 index 00000000..feb845b8 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/AddressList.java @@ -0,0 +1,78 @@ +package net.contargo.iris.address; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +import java.util.List; + + +/** + * Represents a list of addresses. Contains a list of {@link Address}es and a parent Address. + * + * @author Michael Herbold - herbold@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +public class AddressList { + + private final List
addresses; + + private Address parentAddress; + + public AddressList(Address root, List
addresses) { + + this.parentAddress = root; + this.addresses = addresses; + } + + + public AddressList(String name, List
addresses) { + + Address address = new Address(); + address.setDisplayName(name); + + this.parentAddress = address; + this.addresses = addresses; + } + + public List
getAddresses() { + + return addresses; + } + + + public Address getParentAddress() { + + return parentAddress; + } + + + public void setParentAddress(Address parentAddress) { + + this.parentAddress = parentAddress; + } + + + @Override + public int hashCode() { + + return new HashCodeBuilder().append(addresses).append(parentAddress).toHashCode(); + } + + + @Override + public boolean equals(Object obj) { + + if (!(obj instanceof AddressList)) { + return false; + } + + if (this == obj) { + return true; + } + + final AddressList other = (AddressList) obj; + + return new EqualsBuilder().append(this.hashCode(), other.hashCode()).append(this.addresses, other.addresses) + .append(this.parentAddress, other.parentAddress).isEquals(); + } +} diff --git a/src/main/java/net/contargo/iris/address/api/AddressApiController.java b/src/main/java/net/contargo/iris/address/api/AddressApiController.java new file mode 100644 index 00000000..0e2aecf8 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/api/AddressApiController.java @@ -0,0 +1,202 @@ +package net.contargo.iris.address.api; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiParam; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.dto.AddressDto; +import net.contargo.iris.address.dto.AddressDtoService; +import net.contargo.iris.api.AbstractController; + +import org.slf4j.Logger; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; + +import java.math.BigDecimal; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import static net.contargo.iris.address.nominatim.service.AddressDetailKey.*; + +import static org.slf4j.LoggerFactory.getLogger; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + + +/** + * Controller for {@link net.contargo.iris.address.dto.AddressDto}s. + * + * @author Marc Kannegiesser - kannegiesser@synyx.de + * @author Aljona Murygina - murygina@synyx.de + * @author Arnold Franke - franke@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@Controller +@Api(description = "API for querying addresses.", value = "") +public class AddressApiController extends AbstractController { + + public static final String METHOD_ADDRESS_BY_GEOLOCATION = "addressByGeolocation"; + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private final AddressDtoService addressDtoService; + + @Autowired + public AddressApiController(AddressDtoService addressDtoService) { + + this.addressDtoService = addressDtoService; + } + + @ApiOperation( + value = "Get addresses for a given OpenStreetMap-ID.", notes = "Get addresses for a given OpenStreetMap-ID." + ) + @ModelAttribute("geoCodeResponse") + @RequestMapping(value = OSM_ADDRESSES + SLASH + ID_PARAM, method = RequestMethod.GET) + public ListOfAddressListsResponse addressByOsmId( + @ApiParam(value = "ID identifying a single osm-address.", required = true) + @PathVariable(ID) + long osmId) { + + AddressDto address = addressDtoService.getAddressByOsmId(osmId); + + ListOfAddressListsResponse response = new ListOfAddressListsResponse(addressDtoService.wrapInListOfAddressLists( + address)); + + LOG.info("API: Responding to geocode-request for OSM ID {} with {} Blocks", osmId, + response.getAddresses().size()); + + return response; + } + + + // do not remove the last slash of the url, or the longitude decimals will be cut off + @ApiOperation(value = "Get address for the given geolocation.", notes = "Get address for the given geolocation.") + @ModelAttribute("reverseGeocodeResponse") + @RequestMapping( + value = REVERSE_GEOCODE + SLASH + PARAM_LATITUDE + COLON + PARAM_LONGITUDE + SLASH, method = RequestMethod.GET + ) + public AddressResponse addressByGeolocation(@PathVariable("lat") BigDecimal latitude, + @PathVariable("lon") BigDecimal longitude) throws NoSuchMethodException { + + AddressDto address = addressDtoService.getAddressForGeoLocation(new GeoLocation(latitude, longitude)); + AddressResponse response = new AddressResponse(address); + + Method method = AddressApiController.class.getMethod(METHOD_ADDRESS_BY_GEOLOCATION, BigDecimal.class, + BigDecimal.class); + + response.add(linkTo(method, latitude, longitude).slash(".").withSelfRel()); + + LOG.info("API: Responding to request for address by geolocation with latitude {} and longitude {}", latitude, + longitude); + + return response; + } + + + @ApiOperation( + value = "Search list of addresses by address details.", notes = "Search list of addresses by address details." + ) + @ModelAttribute("geoCodeResponse") + @RequestMapping(value = GEOCODES, method = RequestMethod.GET) + public ListOfAddressListsResponse addressesByAddressDetails(@RequestParam(required = false) String street, + @RequestParam(required = false) String postalCode, + @RequestParam(required = false) String city, + @RequestParam(required = false) String country, + @RequestParam(required = false) String name, HttpServletRequest request) { + + Map addressDetails = putRequestParamsToMap(street, postalCode, city, country, name); + + ListOfAddressListsResponse response = new ListOfAddressListsResponse(addressDtoService.getAddressesByDetails( + addressDetails)); + + response.add(linkTo(getClass()).slash(GEOCODES + "?" + request.getQueryString()).withSelfRel()); + + LOG.info("API: Responding to request for address by address details: {}", addressDetails.toString()); + + return response; + } + + + @ApiOperation(value = "Search addresses by address details.", notes = "Search addresses by address details.") + @ModelAttribute("simpleGeoCodeResponse") + @RequestMapping(value = SIMPLE_GEOCODES, method = RequestMethod.GET) + public AddressListResponse addressesByAddressDetailsPlain(@RequestParam(required = false) String street, + @RequestParam(required = false) String postalCode, + @RequestParam(required = false) String city, + @RequestParam(required = false) String country, + @RequestParam(required = false) String name, HttpServletRequest request) { + + Map addressDetails = putRequestParamsToMap(street, postalCode, city, country, name); + + List addressList = addressDtoService.getAddressesByDetailsPlain(addressDetails); + + AddressListResponse response = new AddressListResponse(addressList); + + response.add(linkTo(getClass()).slash(SIMPLE_GEOCODES + "?" + request.getQueryString()).withSelfRel()); + + LOG.info("API: Responding with " + addressList.size() + " addresses to request " + addressDetails.toString()); + + return response; + } + + + @ApiOperation( + value = "Get all addresses belonging to the given OpenStreetMap-Place-ID.", + notes = "Get all addresses belonging to the given OpenStreetMap-Place-ID." + ) + @ModelAttribute("addresses") + @RequestMapping(value = PLACES + SLASH + ID_PARAM + SLASH + ADDRESSES, method = RequestMethod.GET) + public List addressesWherePlaceIsIn( + @ApiParam(value = "ID identifying a osm-place-id.", required = true) + @PathVariable(ID) + long placeId) { + + LOG.info("API: Responding to request for addresses by place id {}", placeId); + + return addressDtoService.getAddressesWherePlaceIsIn(placeId); + } + + + private Map putRequestParamsToMap(String street, String postalCode, String city, String country, + String name) { + + Map addressDetails = new HashMap<>(); + + if (city != null) { + addressDetails.put(CITY.getKey(), city); + } + + if (country != null) { + addressDetails.put(COUNTRY.getKey(), country); + } + + if (postalCode != null) { + addressDetails.put(POSTAL_CODE.getKey(), postalCode); + } + + if (street != null) { + addressDetails.put(STREET.getKey(), street); + } + + if (name != null) { + addressDetails.put(NAME.getKey(), name); + } + + return addressDetails; + } +} diff --git a/src/main/java/net/contargo/iris/address/api/AddressListResponse.java b/src/main/java/net/contargo/iris/address/api/AddressListResponse.java new file mode 100644 index 00000000..7b9a0127 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/api/AddressListResponse.java @@ -0,0 +1,33 @@ +package net.contargo.iris.address.api; + +import net.contargo.iris.address.dto.AddressDto; + +import org.springframework.hateoas.ResourceSupport; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +class AddressListResponse extends ResourceSupport { + + private final List addresses; + + public AddressListResponse(List addresses) { + + this.addresses = addresses; + } + + + public AddressListResponse() { + + addresses = new ArrayList<>(); + } + + public List getAddresses() { + + return addresses; + } +} diff --git a/src/main/java/net/contargo/iris/address/api/AddressResponse.java b/src/main/java/net/contargo/iris/address/api/AddressResponse.java new file mode 100644 index 00000000..6d4aa1d9 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/api/AddressResponse.java @@ -0,0 +1,30 @@ +package net.contargo.iris.address.api; + +import net.contargo.iris.address.dto.AddressDto; + +import org.springframework.hateoas.ResourceSupport; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +class AddressResponse extends ResourceSupport { + + private AddressDto address; + + public AddressResponse(AddressDto address) { + + this.address = address; + } + + + public AddressResponse() { + + // Needed for Jackson Mapping + } + + public AddressDto getAddress() { + + return address; + } +} diff --git a/src/main/java/net/contargo/iris/address/api/ListOfAddressListsResponse.java b/src/main/java/net/contargo/iris/address/api/ListOfAddressListsResponse.java new file mode 100644 index 00000000..43e0bbf8 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/api/ListOfAddressListsResponse.java @@ -0,0 +1,34 @@ +package net.contargo.iris.address.api; + +import net.contargo.iris.address.dto.AddressListDto; + +import org.springframework.hateoas.ResourceSupport; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author Marc Kannegiesser - kannegiesser@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +public class ListOfAddressListsResponse extends ResourceSupport { + + private final List addresses; + + public ListOfAddressListsResponse(List addresses) { + + this.addresses = addresses; + } + + + public ListOfAddressListsResponse() { + + addresses = new ArrayList<>(); + } + + public List getAddresses() { + + return addresses; + } +} diff --git a/src/main/java/net/contargo/iris/address/dto/AddressDto.java b/src/main/java/net/contargo/iris/address/dto/AddressDto.java new file mode 100644 index 00000000..1d72c4e3 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/dto/AddressDto.java @@ -0,0 +1,105 @@ +package net.contargo.iris.address.dto; + +import net.contargo.iris.address.Address; + +import org.apache.commons.lang.builder.HashCodeBuilder; + +import java.util.Map; + +import static java.util.Collections.unmodifiableMap; + + +/** + * Data Transfer Object for {@link Address}. + * + * @author Arnold Franke - franke@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public final class AddressDto extends GeoLocationDto { + + private static final String TYPE = "ADDRESS"; + + private final String displayName; + private final String shortName; + private final String niceName; + private final String countryCode; + private final long osmId; + private final long placeId; + private final Map address; + private final String type; + + public AddressDto(Address address) { + + super(address); + this.countryCode = address.getCountryCode(); + this.niceName = address.getNiceName(); + this.displayName = address.getDisplayName(); + this.osmId = address.getOsmId(); + this.placeId = address.getPlaceId(); + this.shortName = address.getShortName(); + this.address = unmodifiableMap(address.getAddress()); + this.type = TYPE; + } + + public String getShortName() { + + return shortName; + } + + + public String getNiceName() { + + return niceName; + } + + + public Map getAddress() { + + return address; + } + + + public String getDisplayName() { + + return displayName; + } + + + public long getOsmId() { + + return osmId; + } + + + public long getPlaceId() { + + return placeId; + } + + + public String getCountryCode() { + + return countryCode; + } + + + @Override + public String getType() { + + return type; + } + + + @Override + public boolean equals(Object obj) { // NOSONAR We don't want to change the behaviour of base class equals + + return super.equals(obj); // NOSONAR We don't want to change the behaviour of base class equals + } + + + @Override + public int hashCode() { + + return new HashCodeBuilder().append(super.hashCode()).toHashCode(); + } +} diff --git a/src/main/java/net/contargo/iris/address/dto/AddressDtoService.java b/src/main/java/net/contargo/iris/address/dto/AddressDtoService.java new file mode 100644 index 00000000..62d2e546 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/dto/AddressDtoService.java @@ -0,0 +1,77 @@ +package net.contargo.iris.address.dto; + +import net.contargo.iris.GeoLocation; + +import java.util.List; +import java.util.Map; + + +/** + * Delegates to AddressService and converts Address Beans into Address DTOs. + * + * @author Arnold Franke - franke@synyx.de + */ +public interface AddressDtoService { + + /** + * @param osmId + * + * @return The address for a certain osmId + */ + AddressDto getAddressByOsmId(long osmId); + + + /** + * Wraps the given AddressDto as first element of an {@link AddressListDto} This is needed to provide a consistent + * format of returned Addresses for API-Clients. + * + * @param addressDto + * + * @return + */ + List wrapInListOfAddressLists(AddressDto addressDto); + + + /** + * Returns the corresponding {@link AddressDto} object from a given {@link GeoLocation}. + * + * @param location keeps the basis information for the {@link AddressDto} search. + * + * @return The address for the given {@link GeoLocation}. + */ + AddressDto getAddressForGeoLocation(GeoLocation location); + + + /** + * Resolves an address (described by the given parameters) to a {@link java.util.List} of + * {@link net.contargo.iris.address.Address} objects with the attributes name, latitude and longitude. Uses multiple + * fallback strategies to find addresses if not all parameters are provided + * + * @param addressDetails The parameters describing the addresses we are looking for + * + * @return A List of Address Lists + */ + List getAddressesByDetails(Map addressDetails); + + + /** + * Resolves an address (described by the given parameters) to a {@link java.util.List} of + * {@link net.contargo.iris.address.Address} objects with the attributes name, latitude and longitude. Uses multiple + * fallback strategies to find addresses if not all parameters are provided + * + * @param addressDetails The parameters describing the addresses we are looking for + * + * @return A List of Addresses + */ + List getAddressesByDetailsPlain(Map addressDetails); + + + /** + * Returns all Addresses where the given place is in. + * + * @param placeId the OSM Place ID + * + * @return All addresses belonging to the OSM-Place defined by the OSM Place ID + */ + List getAddressesWherePlaceIsIn(Long placeId); +} diff --git a/src/main/java/net/contargo/iris/address/dto/AddressDtoServiceImpl.java b/src/main/java/net/contargo/iris/address/dto/AddressDtoServiceImpl.java new file mode 100644 index 00000000..aa21422e --- /dev/null +++ b/src/main/java/net/contargo/iris/address/dto/AddressDtoServiceImpl.java @@ -0,0 +1,102 @@ +package net.contargo.iris.address.dto; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.Address; +import net.contargo.iris.address.AddressList; +import net.contargo.iris.address.nominatim.service.AddressService; +import net.contargo.iris.address.service.AddressServiceWrapper; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public class AddressDtoServiceImpl implements AddressDtoService { + + private final AddressService addressService; + private final AddressServiceWrapper addressServiceWrapper; + + public AddressDtoServiceImpl(AddressService addressService, AddressServiceWrapper addressServiceWrapper) { + + this.addressService = addressService; + this.addressServiceWrapper = addressServiceWrapper; + } + + @Override + public AddressDto getAddressByOsmId(long osmId) { + + Address address = addressService.getAddressByOsmId(osmId); + + return address == null ? null : new AddressDto(address); + } + + + @Override + public List wrapInListOfAddressLists(AddressDto addressDto) { + + List listOfLists; + + AddressListDto osmList = new AddressListDto("Result", Arrays.asList(addressDto)); + + listOfLists = Arrays.asList(osmList); + + return listOfLists; + } + + + @Override + public AddressDto getAddressForGeoLocation(GeoLocation location) { + + Address address = addressServiceWrapper.getAddressForGeoLocation(location); + + return address == null ? null : new AddressDto(address); + } + + + @Override + public List getAddressesByDetails(Map addressDetails) { + + List addressListList = addressServiceWrapper.getAddressesByDetails(addressDetails); + + List addressListDtoList = new ArrayList<>(); + + for (AddressList addressList : addressListList) { + addressListDtoList.add(new AddressListDto(addressList)); + } + + return addressListDtoList; + } + + + @Override + public List getAddressesByDetailsPlain(Map addressDetails) { + + List addressListList = getAddressesByDetails(addressDetails); + List addressDtoList = new ArrayList<>(); + + for (AddressListDto addressListDto : addressListList) { + addressDtoList.addAll(addressListDto.getAddresses()); + } + + return addressDtoList; + } + + + @Override + public List getAddressesWherePlaceIsIn(Long placeId) { + + List
addressList = addressService.getAdressesWherePlaceIsIn(placeId); + + List addressDtoList = new ArrayList<>(); + + for (Address address : addressList) { + addressDtoList.add(new AddressDto(address)); // NOSONAR Instantiating object is necessary + } + + return addressDtoList; + } +} diff --git a/src/main/java/net/contargo/iris/address/dto/AddressListDto.java b/src/main/java/net/contargo/iris/address/dto/AddressListDto.java new file mode 100644 index 00000000..00b27446 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/dto/AddressListDto.java @@ -0,0 +1,56 @@ +package net.contargo.iris.address.dto; + +import net.contargo.iris.address.Address; +import net.contargo.iris.address.AddressList; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Represents a list of addresses to the API as List of {@link AddressDto} with parent Address. + * + * @author Michael Herbold - herbold@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +public class AddressListDto { + + private final List addresses; + private AddressDto parentAddress; + + public AddressListDto(String name, List addresses) { + + Address address = new Address(name); + this.parentAddress = new AddressDto(address); + this.addresses = addresses; + } + + + public AddressListDto() { + + addresses = new ArrayList<>(); + } + + + public AddressListDto(AddressList addressList) { + + this.parentAddress = new AddressDto(addressList.getParentAddress()); + + this.addresses = new ArrayList<>(); + + for (Address adr : addressList.getAddresses()) { + this.addresses.add(new AddressDto(adr)); + } + } + + public AddressDto getParentAddress() { + + return parentAddress; + } + + + public List getAddresses() { + + return addresses; + } +} diff --git a/src/main/java/net/contargo/iris/address/dto/GeoLocationDto.java b/src/main/java/net/contargo/iris/address/dto/GeoLocationDto.java new file mode 100644 index 00000000..29e9813b --- /dev/null +++ b/src/main/java/net/contargo/iris/address/dto/GeoLocationDto.java @@ -0,0 +1,80 @@ +package net.contargo.iris.address.dto; + +import net.contargo.iris.GeoLocation; + +import org.hibernate.validator.constraints.Range; + +import java.math.BigDecimal; + +import javax.validation.constraints.NotNull; + + +/** + * Data Transfer Object for {@link GeoLocation}. + * + * @author Arnold Franke - franke@synyx.de + */ +public class GeoLocationDto { + + private static final int MAX_VALUE_COORD = 180; + private static final int MIN_VALUE_COORD = -180; + private static final String TYPE = "GEOLOCATION"; + + @NotNull + @Range(min = -MAX_VALUE_COORD, max = MAX_VALUE_COORD) + private BigDecimal latitude; + + @NotNull + @Range(min = MIN_VALUE_COORD, max = MAX_VALUE_COORD) + private BigDecimal longitude; + + public GeoLocationDto(GeoLocation geoLocation) { + + if (geoLocation != null) { + this.latitude = geoLocation.getLatitude(); + this.longitude = geoLocation.getLongitude(); + } + } + + + public GeoLocationDto() { + + // Needed for Jackson Mapping + } + + public GeoLocation toEntity() { + + return new GeoLocation(this.latitude, this.longitude); + } + + + // Setters are needed so this DTO can be used as @ModelAttribute in Spring MVC + public void setLatitude(BigDecimal latitude) { + + this.latitude = latitude; + } + + + public void setLongitude(BigDecimal longitude) { + + this.longitude = longitude; + } + + + public BigDecimal getLatitude() { + + return latitude; + } + + + public BigDecimal getLongitude() { + + return longitude; + } + + + public String getType() { + + return TYPE; + } +} diff --git a/src/main/java/net/contargo/iris/address/nominatim/service/AddressDetailKey.java b/src/main/java/net/contargo/iris/address/nominatim/service/AddressDetailKey.java new file mode 100644 index 00000000..24fff3ea --- /dev/null +++ b/src/main/java/net/contargo/iris/address/nominatim/service/AddressDetailKey.java @@ -0,0 +1,25 @@ +package net.contargo.iris.address.nominatim.service; + +/** + * @author Arnold Franke - franke@synyx.de * + */ +public enum AddressDetailKey { + + STREET("street"), + POSTAL_CODE("postalcode"), + CITY("city"), + COUNTRY("country"), + NAME("name"); + + private String key; + + private AddressDetailKey(String key) { + + this.key = key; + } + + public String getKey() { + + return key; + } +} diff --git a/src/main/java/net/contargo/iris/address/nominatim/service/AddressResolutionException.java b/src/main/java/net/contargo/iris/address/nominatim/service/AddressResolutionException.java new file mode 100644 index 00000000..4b75c979 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/nominatim/service/AddressResolutionException.java @@ -0,0 +1,12 @@ +package net.contargo.iris.address.nominatim.service; + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +public class AddressResolutionException extends RuntimeException { + + public AddressResolutionException(String msg, Throwable t) { + + super(msg, t); + } +} diff --git a/src/main/java/net/contargo/iris/address/nominatim/service/AddressService.java b/src/main/java/net/contargo/iris/address/nominatim/service/AddressService.java new file mode 100644 index 00000000..7f829352 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/nominatim/service/AddressService.java @@ -0,0 +1,54 @@ + +package net.contargo.iris.address.nominatim.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.Address; + +import java.util.List; +import java.util.Map; + + +/** + * This service is used for address resolution purposes. + * + * @author Sven Mueller - mueller@synyx.de + * @author Aljona Murygina - murygina@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +public interface AddressService { + + /** + * Resolves an address (described by the given parameters) to a {@link java.util.List} of + * {@link net.contargo.iris.address.Address} objects with the attributes name, latitude and longitude. Uses multiple + * fallback strategies to find addresses if not all parameters are provided + * + * @param addressDetails @return + */ + List
getAddressesByDetails(Map addressDetails); + + + /** + * Returns all Addresses where the given place is in. + * + * @param placeId the OSM Place ID + * + * @return All addresses belonging to the OSM-Place defined by the OSM Place ID + */ + List
getAdressesWherePlaceIsIn(Long placeId); + + + /** + * @param osmId + * + * @return The address for a certain osmId + */ + Address getAddressByOsmId(long osmId); + + + /** + * @param location + * + * @return The address for the given geolocation. + */ + Address getAddressByGeolocation(GeoLocation location); +} diff --git a/src/main/java/net/contargo/iris/address/nominatim/service/AddressServiceHelper.java b/src/main/java/net/contargo/iris/address/nominatim/service/AddressServiceHelper.java new file mode 100644 index 00000000..967e3d42 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/nominatim/service/AddressServiceHelper.java @@ -0,0 +1,26 @@ +package net.contargo.iris.address.nominatim.service; + +import net.contargo.iris.address.Address; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +/** + * Supporting class for AddressService. + * + * @author Arnold Franke - franke@synyx.de + */ +class AddressServiceHelper { + + List
mergeSearchResultsWithoutDuplications(List
one, List
two) { + + Set
resultSet = new HashSet<>(); + resultSet.addAll(one); + resultSet.addAll(two); + + return new ArrayList<>(resultSet); + } +} diff --git a/src/main/java/net/contargo/iris/address/nominatim/service/AddressSorter.java b/src/main/java/net/contargo/iris/address/nominatim/service/AddressSorter.java new file mode 100644 index 00000000..1c3620e3 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/nominatim/service/AddressSorter.java @@ -0,0 +1,34 @@ +package net.contargo.iris.address.nominatim.service; + +import net.contargo.iris.address.Address; +import net.contargo.iris.gis.service.GisService; + +import java.math.BigDecimal; + +import java.util.Comparator; + + +/** + * Comparator to sort Addresses by distance from the Center of EU to sort the ones outside of the EU to the end. + * + * @author Arnold Franke - franke@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +class AddressSorter implements Comparator
{ + + private final GisService gisService; + + public AddressSorter(GisService gisService) { + + this.gisService = gisService; + } + + @Override + public int compare(Address o1, Address o2) { + + BigDecimal distanceOfO1 = gisService.calcAirLineDistInMeters(o1, GisService.CENTER_OF_THE_EUROPEAN_UNION); + BigDecimal distanceOfO2 = gisService.calcAirLineDistInMeters(o2, GisService.CENTER_OF_THE_EUROPEAN_UNION); + + return distanceOfO1.compareTo(distanceOfO2); + } +} diff --git a/src/main/java/net/contargo/iris/address/nominatim/service/AddressValidator.java b/src/main/java/net/contargo/iris/address/nominatim/service/AddressValidator.java new file mode 100644 index 00000000..4e068214 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/nominatim/service/AddressValidator.java @@ -0,0 +1,25 @@ +package net.contargo.iris.address.nominatim.service; + +import org.slf4j.Logger; + +import java.lang.invoke.MethodHandles; + +import static org.slf4j.LoggerFactory.getLogger; + + +class AddressValidator { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + private static final int MIN_STREET_LENGTH = 3; + + String validateStreet(String street) { + + if (street != null && street.length() < MIN_STREET_LENGTH) { + LOG.info("street='" + street + "' seems to be inaccurate, so it will be ignored during geocoding process."); + + return ""; + } + + return street; + } +} diff --git a/src/main/java/net/contargo/iris/address/nominatim/service/NominatimAddressService.java b/src/main/java/net/contargo/iris/address/nominatim/service/NominatimAddressService.java new file mode 100644 index 00000000..55a13952 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/nominatim/service/NominatimAddressService.java @@ -0,0 +1,196 @@ +package net.contargo.iris.address.nominatim.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.Address; +import net.contargo.iris.util.HttpUtilException; + +import org.slf4j.Logger; + +import org.springframework.util.StringUtils; + +import java.lang.invoke.MethodHandles; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static net.contargo.iris.address.nominatim.service.AddressDetailKey.CITY; +import static net.contargo.iris.address.nominatim.service.AddressDetailKey.COUNTRY; +import static net.contargo.iris.address.nominatim.service.AddressDetailKey.NAME; +import static net.contargo.iris.address.nominatim.service.AddressDetailKey.POSTAL_CODE; +import static net.contargo.iris.address.nominatim.service.AddressDetailKey.STREET; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * An implementation of the {@link AddressService} interface using Nominatim to resolveWithNominatim an address to + * geocoordinates and jackson to generate Java objects from Json. Nominatim: http://nominatim.openstreetmap.org + * + * @author Aljona Murygina - murygina@synyx.de + * @author Oliver Messner - messner@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +public class NominatimAddressService implements AddressService { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private static final int ARGUMENT_4 = 4; + private static final int ARGUMENT_3 = 3; + private static final int ARGUMENT_2 = 2; + private static final int ARGUMENT_1 = 1; + private static final int ARGUMENT_0 = 0; + + private final NominatimUrlBuilder nominatimUrlBuilder; + private final NominatimJsonResponseParser nominatimResponder; + + private final AddressSorter addressSorter; + private final AddressServiceHelper addressHelper; + private final AddressValidator addressValidator; + + public NominatimAddressService(NominatimUrlBuilder nominatimUrlBuilder, + NominatimJsonResponseParser nominatimResponder, AddressSorter addressSorter, AddressServiceHelper addressHelper, + AddressValidator addressValidator) { + + this.nominatimUrlBuilder = nominatimUrlBuilder; + this.nominatimResponder = nominatimResponder; + this.addressSorter = addressSorter; + this.addressHelper = addressHelper; + this.addressValidator = addressValidator; + } + + List
resolveWithNominatim(String street, String postalCode, String city, String country, String name) { + + /* + * Please notice that Nominatim has no search results if query contains name and street. + * If name should be a search parameter street and streetNumber have to be ignored. + */ + + if (StringUtils.hasText(name) && StringUtils.hasText(street)) { + return geocodeByName(street, postalCode, city, country, name); + } else { + String url = nominatimUrlBuilder.buildUrl(street, postalCode, city, country, name); + + List
addresses = nominatimResponder.getAddressesForUrl(url); + + Collections.sort(addresses, addressSorter); + + return addresses; + } + } + + + @Override + public List
getAdressesWherePlaceIsIn(Long placeId) { + + return searchSuburbsViaNominatimsDetailPage(placeId, SuburbType.ADDRESSES, new HashSet()); + } + + + @Override + public Address getAddressByOsmId(long osmId) { + + String suburbUrl = nominatimUrlBuilder.buildOsmUrl(osmId); + List
foundAddresses = nominatimResponder.getAddressesForUrlForOsmId(suburbUrl); + + return foundAddresses.get(0); + } + + + @Override + public Address getAddressByGeolocation(GeoLocation geoLocation) { + + try { + String url = nominatimUrlBuilder.buildUrl(geoLocation); + + return nominatimResponder.getAddressForUrl(url); + } catch (IllegalArgumentException | HttpUtilException e) { + throw new AddressResolutionException("Failed to resolve address for " + geoLocation, e); + } + } + + + private List
searchSuburbsViaNominatimsDetailPage(Long osmPlaceId, SuburbType suburbType, + Set suburbGlobalDisplayNames) { + + List
suburbs = new ArrayList<>(); + + String suburbUrl = nominatimUrlBuilder.buildSuburbUrl(osmPlaceId, suburbType.getType()); + List
foundSuburbs = nominatimResponder.getAddressesForUrl(suburbUrl); + + if (!foundSuburbs.isEmpty()) { + // check for possible redundant address display names + for (Address foundSuburb : foundSuburbs) { + if (!foundSuburb.getDisplayName().contains("No Name") + && !suburbGlobalDisplayNames.contains(foundSuburb.getDisplayName())) { + // add to suburbs list + suburbs.add(foundSuburb); + + // add to golbal display names, for next iteration + suburbGlobalDisplayNames.add(foundSuburb.getDisplayName()); + } + } + } + + return suburbs; + } + + + private List
geocodeByName(String street, String postalCode, String city, String country, String name) { + + // make 2 querys: 1 query for search by name, 1 query for search by street + String url1 = nominatimUrlBuilder.buildUrl(null, postalCode, city, country, name); + String url2 = nominatimUrlBuilder.buildUrl(street, postalCode, city, country, null); + + List
one = nominatimResponder.getAddressesForUrl(url1); + List
two = nominatimResponder.getAddressesForUrl(url2); + + // avoid duplication of places by osm_id + List
mergedList = addressHelper.mergeSearchResultsWithoutDuplications(one, two); + + // sort list so that unplausible results (e.g., not in europe) appear last + Collections.sort(mergedList, addressSorter); + + return mergedList; + } + + + @Override + public List
getAddressesByDetails(Map addressDetails) { + + String[][] resolvingStrategies = createResolvingStrategies(addressDetails.get(POSTAL_CODE.getKey()), + addressDetails.get(CITY.getKey()), addressDetails.get(COUNTRY.getKey()), + addressDetails.get(NAME.getKey()), + addressValidator.validateStreet(addressDetails.get(STREET.getKey()))); + + for (String[] args : resolvingStrategies) { + List
addresses = resolveWithNominatim(args[ARGUMENT_0], args[ARGUMENT_1], args[ARGUMENT_2], + args[ARGUMENT_3], args[ARGUMENT_4]); + + if (!addresses.isEmpty()) { + return addresses; + } + } + + LOG.info("No results for city " + addressDetails.get(CITY.getKey()) + " and country " + + addressDetails.get(COUNTRY.getKey()) + + ". Returning empty result."); + + return Collections.emptyList(); + } + + + private String[][] createResolvingStrategies(String postalCode, String city, String country, String name, + String internStreet) { + + return new String[][] { + { internStreet, postalCode, city, country, name }, + { internStreet, null, city, country, null }, + { null, postalCode, city, country, null }, + { null, null, city, country, null } + }; + } +} diff --git a/src/main/java/net/contargo/iris/address/nominatim/service/NominatimJsonResponseParser.java b/src/main/java/net/contargo/iris/address/nominatim/service/NominatimJsonResponseParser.java new file mode 100644 index 00000000..f269f49b --- /dev/null +++ b/src/main/java/net/contargo/iris/address/nominatim/service/NominatimJsonResponseParser.java @@ -0,0 +1,97 @@ +package net.contargo.iris.address.nominatim.service; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import net.contargo.iris.address.Address; +import net.contargo.iris.util.HttpUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +import java.lang.invoke.MethodHandles; + +import java.util.List; + + +/** + * The {@link NominatimJsonResponseParser} sends requests to Nominatim and converts the responses to {@link Address} + * objects. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +class NominatimJsonResponseParser { + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final HttpUtil httpUtil; + private final ObjectMapper objectMapper; + + public NominatimJsonResponseParser(HttpUtil httpUtil, ObjectMapper objectMapper) { + + this.httpUtil = httpUtil; + this.objectMapper = objectMapper; + } + + /** + * Returns {@link java.util.List} of {@link Address}es for the given URL. Returns an empty {@link java.util.List} if + * there are no search results. + * + * @param url String + */ + List
getAddressesForUrl(String url) { + + return convertContentToAddresses(url, httpUtil.getResponseContent(url)); + } + + + /** + * Extracts the addresses from the URL. + * + * @param url to extract the address + * + * @return the extracted addresses + */ + List
getAddressesForUrlForOsmId(String url) { + + return convertContentToAddresses(url, "[" + httpUtil.getResponseContent(url) + "]"); + } + + + Address getAddressForUrl(String reverseGeoCodingLookupURL) { + + String content = httpUtil.getResponseContent(reverseGeoCodingLookupURL); + LOG.debug("Got result for reverse Geo coding lookup: {}", content); + + Address address; + + try { + address = objectMapper.readValue(content, Address.class); + } catch (IOException | NullPointerException e) { + address = null; + } + + return address; + } + + + private List
convertContentToAddresses(String url, String content) { + + List
addresses = null; + + if (content != null) { + try { + addresses = objectMapper.readValue(content, new TypeReference>() { + }); + LOG.debug("{} search result(s) found for URL {}", addresses.size(), url); + } catch (IOException e) { + addresses = null; + } + } + + return addresses; + } +} diff --git a/src/main/java/net/contargo/iris/address/nominatim/service/NominatimUrlBuilder.java b/src/main/java/net/contargo/iris/address/nominatim/service/NominatimUrlBuilder.java new file mode 100644 index 00000000..85afa773 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/nominatim/service/NominatimUrlBuilder.java @@ -0,0 +1,245 @@ +package net.contargo.iris.address.nominatim.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.countries.service.CountryService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.io.UnsupportedEncodingException; + +import java.lang.invoke.MethodHandles; + +import java.net.URLEncoder; + +import java.util.Map; + + +/** + * To geocode an address {@link NominatimAddressService} needs a URL containing the base URL, different parameters and + * the search query. In the application context the variable parameter are defined (like email and base url), the + * requested format is always json since the {@link NominatimJsonResponseParser} creates Java objects by json. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Vincent Potucek - potucek@synyx.de + */ +class NominatimUrlBuilder { + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final String SEARCH_URL = "search.php?q="; + private static final String SUBURB_URL = "suburb.php?place_id="; + private static final String SUBURB_TYPE = "&suburb_type="; + private static final String ACCEPT_LANGUAGE = "&accept-language="; + private static final String FORMAT = "&format=json"; + private static final String ADDRESS_DETAILS = "&addressdetails=1"; + private static final String COUNTRY = "&countrycodes="; + private static final String WHITESPACE = " "; + private static final String OSMTYPE_W = "&osm_type=W"; + private static final String OSMID = "osm_id="; + private static final String REVERSE_URL = "reverse?"; + private static final String LOG_TWO_PLACEHOLDERS = "{}{}"; + private static final String LOG_BUILT_URL = "Built URL: "; + + private final String baseUrl; + private final String language; + private final CountryService countryService; + + NominatimUrlBuilder(String baseUrl, String language, CountryService countryService) { + + this.countryService = countryService; + + Assert.hasText(baseUrl); + Assert.hasText(language); + + this.baseUrl = baseUrl; + this.language = language; + } + + /** + * Builds Nominatim's search url appending the query to the base URL. + * + * @param street + streetNumber + * @param postalCode + * @param city + * @param country + * @param name + * + * @return ready URL to send a GET request to Nominatim + */ + String buildUrl(String street, String postalCode, String city, String country, String name) { + + String url = baseUrl + SEARCH_URL + buildQuery(street, postalCode, city, name) + COUNTRY + + encodeUrl(buildCountryCodeList(country)) + FORMAT + ADDRESS_DETAILS + ACCEPT_LANGUAGE + language; + + LOG.debug(LOG_TWO_PLACEHOLDERS, LOG_BUILT_URL, url); + + return url; + } + + + private String buildCountryCodeList(String country) { + + if (StringUtils.hasText(country)) { + return country; + } else { + Map cc = countryService.getCountries(); + + return String.join(",", cc.values()); + } + } + + + /** + * Builds Nominatim's suburb url (modified details page) using the given place ID and suburb type. + * + * @param placeId long + * @param suburbType String + * + * @return ready URL to send a GET request to Nominatim + */ + String buildSuburbUrl(long placeId, String suburbType) { + + String suburbUrl = baseUrl + SUBURB_URL + placeId + FORMAT + ADDRESS_DETAILS + SUBURB_TYPE + suburbType; + LOG.debug(LOG_TWO_PLACEHOLDERS, LOG_BUILT_URL, suburbUrl); + + return suburbUrl; + } + + + /** + * Appends the query to the base URL using Nominatim's special phrases like 'suburb' for specific searching. + * + * @param city the city + * @param specialPhrase 'suburb' or 'village' or any other specific place type + * + * @return ready URL to send a GET request to Nominatim + */ + String buildSpecialPhrasesUrl(String city, String specialPhrase) { + + String specialPhrasesUrl = baseUrl + SEARCH_URL + buildSpecialPhraseQuery(city, specialPhrase) + "&limit=100" + + FORMAT + ACCEPT_LANGUAGE + language + ADDRESS_DETAILS; + LOG.debug(LOG_TWO_PLACEHOLDERS, LOG_BUILT_URL, specialPhrasesUrl); + + return specialPhrasesUrl; + } + + + /** + * Build the osm url within the osm id. + * + * @param osmId to build osm url + * + * @return The URL for requesting ONE Address by osmId. The osm Type used is "N" for Node by default. + */ + String buildOsmUrl(long osmId) { + + String osmUrl = baseUrl + REVERSE_URL + OSMID + osmId + OSMTYPE_W + FORMAT; + LOG.debug(LOG_TWO_PLACEHOLDERS, LOG_BUILT_URL, osmUrl); + + return osmUrl; + } + + + /** + * Builds the query and converts it to URL encoded format with {@link java.net.URLEncoder} (e.g. replaces + * whitespaces by plus and commas by %2C). + * + * @param street: street name + house number + * @param postalCode + * @param city + * @param name + * + * @return search query String + */ + String buildQuery(String street, String postalCode, String city, String name) { + + StringBuilder queryBuilder = new StringBuilder(); + appendStringWithSeparatorIfHasText(queryBuilder, street, ","); + appendStringWithSeparatorIfHasText(queryBuilder, postalCode, WHITESPACE); + appendStringWithSeparatorIfHasText(queryBuilder, city, WHITESPACE); + appendStringWithSeparatorIfHasText(queryBuilder, name, WHITESPACE); + + String query = queryBuilder.toString(); + String encodedUrl = encodeUrl(query); + + return encodedUrl == null ? query : encodedUrl; + } + + + /** + * Builds the query with special phrase and city and converts it to URL encoded format with + * {@link java.net.URLEncoder}. + * + * @param city + * @param specialPhrase + * + * @return special phrases query + */ + String buildSpecialPhraseQuery(String city, String specialPhrase) { + + StringBuilder queryBuilder = new StringBuilder(); + + appendStringWithSeparatorIfHasText(queryBuilder, specialPhrase, WHITESPACE); + appendStringWithSeparatorIfHasText(queryBuilder, city, ""); + + String query = queryBuilder.toString(); + String encodedUrl = encodeUrl(query); + + return encodedUrl == null ? query : encodedUrl; + } + + + /** + * Encodes an URL with {@link java.net.URLEncoder}. + * + * @param url String + * + * @return encoded URL String + */ + private String encodeUrl(String url) { + + String result = null; + + try { + result = URLEncoder.encode(url, "UTF-8"); + } catch (UnsupportedEncodingException ex) { + LOG.warn("Could not encode this String to URL: {} ", url); + } + + return result; + } + + + /** + * If the given String has text, append it with the given separator to the StringBuilder. + * + * @param builder + * @param string + * @param separator + */ + void appendStringWithSeparatorIfHasText(StringBuilder builder, String string, String separator) { + + if (StringUtils.hasText(string)) { + builder.append(string).append(separator); + } + } + + + String buildUrl(GeoLocation geoLocation) { + + if (geoLocation.getLatitude() == null || geoLocation.getLongitude() == null) { + throw new IllegalArgumentException("Invalid Geo location: " + geoLocation); + } + + String url = baseUrl + "reverse/" + "?format=json" + "&lat=" + geoLocation.getLatitude().toString() + "&lon=" + + geoLocation.getLongitude().toString(); + + LOG.debug("Built request URL: {}", url); + + return url; + } +} diff --git a/src/main/java/net/contargo/iris/address/nominatim/service/SuburbType.java b/src/main/java/net/contargo/iris/address/nominatim/service/SuburbType.java new file mode 100644 index 00000000..d3079000 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/nominatim/service/SuburbType.java @@ -0,0 +1,26 @@ +package net.contargo.iris.address.nominatim.service; + +/** + * Enum for suburb types [suburb|administrative|village]. + * + * @author Michael Herbold - herbold@synyx.de + */ +enum SuburbType { + + ADDRESSES("addresses"), + SUBURB("suburb"), + ADMINISTRATIVE("administrative"), + VILLAGE("village"); + + private String type; + + private SuburbType(String type) { + + this.type = type; + } + + public String getType() { + + return this.type; + } +} diff --git a/src/main/java/net/contargo/iris/address/service/AddressCache.java b/src/main/java/net/contargo/iris/address/service/AddressCache.java new file mode 100644 index 00000000..a31f7123 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/service/AddressCache.java @@ -0,0 +1,78 @@ +package net.contargo.iris.address.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.Address; +import net.contargo.iris.address.AddressList; + +import org.springframework.cache.Cache; + +import java.math.BigDecimal; + +import java.util.List; + + +/** + * Wraps a Cache to save resolved Address instances. + * + * @author Marc Kannegiesser - kannegiesser@synyx.de + */ +class AddressCache { + + private final Cache cache; + + public AddressCache(Cache cache) { + + this.cache = cache; + } + + /** + * Caches all the Addresses. + * + * @param addressListList + */ + public void cache(List addressListList) { + + for (AddressList list : addressListList) { + for (Address address : list.getAddresses()) { + cache.put(getAddressLocationBasedHash(address), address); + } + } + } + + + /** + * Returns the Address-Instance for the given GeoLocation IF it exists in the cache - null otherwise. + * + * @param loc + * + * @return + */ + public Address getForLocation(GeoLocation loc) { + + Cache.ValueWrapper valWrapper = cache.get(getAddressLocationBasedHash(loc)); + + if (valWrapper == null) { + return null; + } else { + return (Address) valWrapper.get(); + } + } + + + private String getAddressLocationBasedHash(GeoLocation address) { + + BigDecimal latitude = new BigDecimal(-1); + + if (address.getLatitude() != null) { + latitude = address.getLatitude(); + } + + BigDecimal longitude = new BigDecimal(-1); + + if (address.getLongitude() != null) { + longitude = address.getLongitude(); + } + + return latitude + ":" + longitude; + } +} diff --git a/src/main/java/net/contargo/iris/address/service/AddressServiceWrapper.java b/src/main/java/net/contargo/iris/address/service/AddressServiceWrapper.java new file mode 100644 index 00000000..b6bbf2a0 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/service/AddressServiceWrapper.java @@ -0,0 +1,161 @@ +package net.contargo.iris.address.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.Address; +import net.contargo.iris.address.AddressList; +import net.contargo.iris.address.nominatim.service.AddressService; +import net.contargo.iris.address.staticsearch.StaticAddress; +import net.contargo.iris.address.staticsearch.service.StaticAddressService; +import net.contargo.iris.normalizer.NormalizerService; + +import org.apache.commons.lang.StringUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static net.contargo.iris.address.nominatim.service.AddressDetailKey.CITY; +import static net.contargo.iris.address.nominatim.service.AddressDetailKey.COUNTRY; +import static net.contargo.iris.address.nominatim.service.AddressDetailKey.POSTAL_CODE; +import static net.contargo.iris.address.nominatim.service.AddressDetailKey.STREET; + + +/** + * Wrapper class to have better control about the resolving methods of {@link AddressService}. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Oliver Messner - messner@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +public class AddressServiceWrapper { + + private static final Logger LOG = LoggerFactory.getLogger(AddressServiceWrapper.class); + + private final AddressService addressService; + private final StaticAddressService staticAddressService; + private final AddressCache addressCache; + private final NormalizerService normalizerService; + + public AddressServiceWrapper(AddressService addressService, StaticAddressService staticAddressService, + AddressCache cache, NormalizerService normalizerService) { + + this.addressService = addressService; + this.staticAddressService = staticAddressService; + this.addressCache = cache; + this.normalizerService = normalizerService; + } + + /** + * Searches an {@link Address} by the given parameters of the {@link GeoLocation} and returns it. + * + * @param geoLocation basis for the search of the {@link Address} + * + * @return The address for the given {@link GeoLocation}. + */ + public Address getAddressForGeoLocation(GeoLocation geoLocation) { + + Address address = addressCache.getForLocation(geoLocation); + + if (address != null) { + return address; + } + + StaticAddress staticAddress = staticAddressService.getForLocation(geoLocation); + + if (staticAddress != null) { + return staticAddress.toAddress(); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Cache miss: {}", geoLocation); + } + + address = addressService.getAddressByGeolocation(geoLocation); + + if (address != null) { + // the geo coordinates in the returned 'address' object differ from + // the geo coordinates in the 'loc' object + address.setLatitude(geoLocation.getLatitude()); + address.setLongitude(geoLocation.getLongitude()); + addressCache.cache(getSimpleAddressList(Arrays.asList(address))); + } + + return address; + } + + + /** + * Searches with the given parameters from the addressDetails map like street, postalcode and city the + * geocoordinates and returns a list of {@link AddressList} with their geocoordinates. To improve the result quality + * of geocoding services, it's sometimes better to make the search not with all the given parameters. (e.g. try to + * get address using only street and city) + * + * @param addressDetails keeps the search information + * + * @return list of {@link AddressList} + */ + public List getAddressesByDetails(Map addressDetails) { + + List result = new ArrayList<>(); + + String cityNormalized = normalizerService.normalize(addressDetails.get(CITY.getKey())); + String postalCode = addressDetails.get(POSTAL_CODE.getKey()); + String country = addressDetails.get(COUNTRY.getKey()); + String street = addressDetails.get(STREET.getKey()); + + if (StringUtils.isNotEmpty(postalCode) || StringUtils.isNotEmpty(cityNormalized)) { + result.add(resolveByStaticAddressService(postalCode, cityNormalized, country)); + } + + if (street != null && !"".equals(street)) { + result.addAll(resolveByNominatim(addressDetails)); + } + + addressCache.cache(result); + + return result; + } + + + private AddressList resolveByStaticAddressService(String postalCode, String city, String country) { + + return staticAddressService.findAddresses(postalCode, city, country); + } + + + private List resolveByNominatim(Map addressDetails) { + + List
addresses = addressService.getAddressesByDetails(addressDetails); + + if (addresses == null || addresses.isEmpty()) { + LOG.info("No results for city " + addressDetails.get(CITY.getKey()) + " and country " + + addressDetails.get(COUNTRY.getKey()) + + ". Returning empty result."); + + return Collections.emptyList(); + } + + return getSimpleAddressList(addresses); + } + + + private List getSimpleAddressList(List
addresses) { + + List addressListList = new ArrayList<>(); + + for (Address address : addresses) { + List
result = new ArrayList<>(); + result.add(address); + + AddressList addressesList = new AddressList(address, result); + addressListList.add(addressesList); + } + + return addressListList; + } +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/StaticAddress.java b/src/main/java/net/contargo/iris/address/staticsearch/StaticAddress.java new file mode 100644 index 00000000..f3be2a0d --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/StaticAddress.java @@ -0,0 +1,372 @@ +package net.contargo.iris.address.staticsearch; + +import com.google.common.base.Strings; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.Address; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +import org.hibernate.validator.constraints.NotBlank; + +import org.springframework.util.StringUtils; + +import java.math.BigInteger; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import javax.validation.constraints.Size; + + +/** + * Entity for static addresses (csv list). + * + * @author Michael Herbold - herbold@synyx.de + * @author Sandra Thieme - thieme@synyx.de + * @author Oliver Messner - messner@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +@Entity(name = "StaticAddress") +// NOSONAR Don't want to override equals +public class StaticAddress extends GeoLocation { + + public static final String STATIC_ID = "static_id"; + + private static final int POSTAL_CODE_MAX_SIZE = 10; + private static final int COUNTRY_MAX_SIZE = 5; + private static final int HASHKEY_MAX_SIZE = 5; + private static final int CITY_MAX_SIZE = 255; + private static final int SUBURB_MAX_SIZE = 255; + private static final int HASH_KEY_SIZE = 5; + private static final int BASE_36 = 36; + private static final int BASE_10 = 10; + private static final int BEGIN_INDEX_0 = 0; + private static final int END_INDEX_4 = 4; + private static final int SHIFT_20 = 20; + private static final int LENGTH_AUTOINCREMENTED_PART = 12; + private static final int BINARY_111111 = 63; + private static final int SIZE_TO_THE_PAD = 16; + + @Id + @GeneratedValue + private Long id; + + @Size(max = COUNTRY_MAX_SIZE) + private String country; + + @NotBlank + @Size(max = POSTAL_CODE_MAX_SIZE) + private String postalcode; + + @NotBlank + @Size(max = CITY_MAX_SIZE) + private String city; + + @NotBlank + @Size(max = CITY_MAX_SIZE) + private String cityNormalized; + + @Size(max = SUBURB_MAX_SIZE) + private String suburb; + + @Size(max = SUBURB_MAX_SIZE) + private String suburbNormalized; + + @Size(max = HASHKEY_MAX_SIZE) + private String hashKey; + + private BigInteger uniqueId; + + public Address toAddress() { + + Address address = new Address(); + + injectDisplayName(address); + address.getAddress().put(Address.COUNTRY_CODE, getCountry()); + + if (postalcode != null) { + address.getAddress().put("postcode", postalcode); + } + + if (city != null) { + address.getAddress().put("city", city); + } + + if (suburb != null) { + address.getAddress().put("suburb", suburb); + } + + address.getAddress().put("hashkey", getHashKey()); + address.getAddress().put(STATIC_ID, uniqueId == null ? null : uniqueId.toString()); + address.setLongitude(getLongitude()); + address.setLatitude(getLatitude()); + + return address; + } + + + private void injectDisplayName(Address address) { + + if (StringUtils.hasText(suburb)) { + address.setDisplayName(String.format("%s %s (%s)", extractString(postalcode), extractString(city), + extractString(suburb))); + } else { + address.setDisplayName(String.format("%s %s", extractString(postalcode), extractString(city))); + } + } + + + private String extractString(String value) { + + return value == null || value.isEmpty() ? "" : value; + } + + + public Long getId() { + + return id; + } + + + public void setId(Long id) { + + this.id = id; + } + + + public String getCountry() { + + return country; + } + + + public void setCountry(String country) { + + this.country = country; + } + + + public String getPostalcode() { + + return postalcode; + } + + + public void setPostalcode(String postalcode) { + + this.postalcode = postalcode; + } + + + public String getCity() { + + return city; + } + + + public void setCity(String city) { + + this.city = city; + } + + + public String getCityNormalized() { + + return cityNormalized; + } + + + public void setCityNormalized(String cityNormalized) { + + this.cityNormalized = cityNormalized; + } + + + public String getSuburb() { + + return suburb; + } + + + public void setSuburb(String suburb) { + + this.suburb = suburb; + } + + + public String getSuburbNormalized() { + + return suburbNormalized; + } + + + public void setSuburbNormalized(String suburbNormalized) { + + this.suburbNormalized = suburbNormalized; + } + + + public String getHashKey() { + + if (hashKey == null || "".equals(hashKey)) { + hashKey = generateHashKey(); + } + + return hashKey; + } + + + /** + * Generates a quasi unique "hashKey" from this {@link StaticAddress}'s uniqueId. The Hash Key is no real hash but a + * mapping. The Hash Key consists of a 5 digit Base36 number. 5 digit Base36 fits into 26 bit binary. The Hash Key + * is assembled in binary by Java bitwise operators and then converted to Base36. + * + *

Assembly of the 26 bit binary HashKey:

+ * + *

The first 6 bits are the first 4 digits of the uniqueId ( representing the systemId) mapped to a 6 digit + * binary number. This is only duplicate-free for a small number of possible systemIds.

+ * + *

The last 20 bits are the autoincremented last 12 digits of the uniqueId. This works up to 1048575 (2^20)

+ * + * @return the uniqueId mapped to a Base36 "hashKey" + */ + private String generateHashKey() { + + if (this.uniqueId == null) { + return ""; + } + + String uniqueIdString = this.uniqueId.toString(); + uniqueIdString = org.apache.commons.lang.StringUtils.leftPad(uniqueIdString, SIZE_TO_THE_PAD, '0'); + + long systemId = Long.valueOf(uniqueIdString.substring(BEGIN_INDEX_0, END_INDEX_4)); + long systemIdInBinary = mapToSixBits(systemId); + long uniqueIdAutoIncrementedPartInBinary = getAutoIncrementedPart(uniqueIdString); + + long hashKeyInBinary = joinBinaryNumbers(systemIdInBinary, uniqueIdAutoIncrementedPartInBinary); + + return convertToBase36(hashKeyInBinary); + } + + + private String convertToBase36(long hashkeyInBinary) { + + String hashKeyInBase36 = conv(String.valueOf(hashkeyInBinary), BASE_10, BASE_36); + hashKeyInBase36 = Strings.padStart(hashKeyInBase36, HASH_KEY_SIZE, '0'); + + return hashKeyInBase36.toUpperCase(); + } + + + private long getAutoIncrementedPart(String uniqueIdString) { + + return Long.valueOf(uniqueIdString.substring(uniqueIdString.length() - LENGTH_AUTOINCREMENTED_PART + 1, + uniqueIdString.length())); + } + + + private long joinBinaryNumbers(long systemIdInBit, long uniqueIdAutoIncrementedPart) { + + // Append 20 0-bits to the systemId so there is room for adding a larger number (up to 20 bits) + long finalNumberOfBitsWithSystemId = systemIdInBit << SHIFT_20; + + // fill the 0-bits with the AutoIncremented part of the uniqueId. Works for every number not larger than 2^20 + return finalNumberOfBitsWithSystemId | uniqueIdAutoIncrementedPart; + } + + + private long mapToSixBits(long systemId) { + + // Has to map the systemId to exactly 6 bits without duplication + // Get the last 6 bits of the systemId by applying a binary AND with 111111. We need the 6 last bits because + // the currently known systemIds have conflicts in the last 5 bits. + return systemId & BINARY_111111; + } + + + public BigInteger getUniqueId() { + + return uniqueId; + } + + + public void setUniqueId(BigInteger uniqueId) { + + this.uniqueId = uniqueId; + hashKey = generateHashKey(); + } + + + String conv(String input, int fromBase, int toBase) { + + try { + int inputInFromBase = Integer.parseInt(input, fromBase); + + return Integer.toString(inputInFromBase, toBase); + } catch (NumberFormatException e) { + return null; + } + } + + + @Override + public boolean equals(Object obj) { + + return new EqualsBuilder().appendSuper(super.equals(obj)).isEquals(); + } + + + @Override + public int hashCode() { + + return new HashCodeBuilder().appendSuper(super.hashCode()).toHashCode(); + } + + + @Override + public String toString() { + + return "StaticAddress [id=" + id + ", country=" + country + + ", postalCode=" + postalcode + ", city=" + city + + ", cityNormalized=" + cityNormalized + ", suburb=" + suburb + + ", suburbNormalized=" + suburbNormalized + ", latitude=" + getLatitude() + ", longitude=" + getLongitude() + + "]"; + } + + + /** + * Check for changed address parameters (suburb, city, postalcode). + * + * @param staticAddress to check + * + * @return true, if address parameters are different + */ + public boolean areAddressParametersDifferent(StaticAddress staticAddress) { + + EqualsBuilder builder = new EqualsBuilder(); + builder.append(this.suburb, staticAddress.getSuburb()); + builder.append(this.city, staticAddress.getCity()); + builder.append(this.postalcode, staticAddress.getPostalcode()); + + return !builder.isEquals(); + } + + + /** + * Check for changed parameters longitude and latitude. + * + * @param staticAddress to check + * + * @return true, if parameters are different + */ + public boolean areLatitudeAndLongitudeDifferent(StaticAddress staticAddress) { + + boolean latitudeEqual = this.getLatitude().compareTo(staticAddress.getLatitude()) == 0; + boolean longitudeEqual = this.getLongitude().compareTo(staticAddress.getLongitude()) == 0; + + return !(latitudeEqual && longitudeEqual); + } +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/api/StaticAddressApiController.java b/src/main/java/net/contargo/iris/address/staticsearch/api/StaticAddressApiController.java new file mode 100644 index 00000000..179d8335 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/api/StaticAddressApiController.java @@ -0,0 +1,136 @@ +package net.contargo.iris.address.staticsearch.api; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.api.ListOfAddressListsResponse; +import net.contargo.iris.address.dto.AddressDto; +import net.contargo.iris.address.staticsearch.dto.StaticAddressDtoService; +import net.contargo.iris.address.staticsearch.dto.StaticAddressesResponse; +import net.contargo.iris.address.staticsearch.dto.StaticAddressesUidResponse; +import net.contargo.iris.api.AbstractController; + +import org.slf4j.Logger; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.lang.invoke.MethodHandles; + +import java.math.BigDecimal; + +import java.util.Collection; + +import static net.contargo.iris.api.AbstractController.SLASH; +import static net.contargo.iris.api.AbstractController.STATIC_ADDRESSES; + +import static org.slf4j.LoggerFactory.getLogger; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + + +/** + * Controller for the public API of {@link net.contargo.iris.address.dto.AddressDto}s that belong to static addresses. + * + * @author Arnold Franke - franke@synyx.de + * @author David Schilling - schilling@synyx.de + */ +@Api(value = SLASH + STATIC_ADDRESSES, description = "API for interaction with static addresses.") +@Controller +@RequestMapping(SLASH + STATIC_ADDRESSES) +public class StaticAddressApiController extends AbstractController { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + private static final String LAT = "lat"; + private static final String LON = "lon"; + + private final StaticAddressDtoService staticAddressDtoService; + + @Autowired + public StaticAddressApiController(StaticAddressDtoService staticAddressDtoService) { + + this.staticAddressDtoService = staticAddressDtoService; + } + + @ApiOperation(value = "Returns all static addresses.", notes = "Returns all static addresses.") + @RequestMapping(method = RequestMethod.GET) + public StaticAddressesResponse getAll() { + + StaticAddressesResponse response = new StaticAddressesResponse(); + response.setAddressDtoList(staticAddressDtoService.getAll()); + + response.add(linkTo(getClass()).withSelfRel()); + + return response; + } + + + @ApiOperation( + value = "Returns all static addresses filtered by postalcode, city and country.", + notes = "Returns all static addresses filtered by postalcode, city and country.", response = AddressDto.class, + responseContainer = "List" + ) + @RequestMapping(method = RequestMethod.GET, params = { "postalCode", "city", "country" }) + @ResponseBody + public Collection getByPostalCodeAndCityAndCountry( + @RequestParam(value = "postalCode") String postalCode, + @RequestParam(value = "city") String city, + @RequestParam(value = "country") String country) { + + return staticAddressDtoService.getAddressesByDetails(postalCode, city, country); + } + + + @ApiOperation( + value = "Returns all static addresses filtered by the given geolocation.", + notes = "Returns all static addresses filtered by the given geolocation." + ) + @ModelAttribute("geoCodeResponse") + @RequestMapping(method = RequestMethod.GET, params = { LAT, LON }) + public ListOfAddressListsResponse getStaticAddressByGeolocation(@RequestParam(LAT) BigDecimal latitude, + @RequestParam(LON) BigDecimal longitude) { + + GeoLocation location = new GeoLocation(latitude, longitude); + ListOfAddressListsResponse response = new ListOfAddressListsResponse( + staticAddressDtoService.getStaticAddressByGeolocation(location)); + + LOG.info("API: Responding to geocode-request for geolocation {} with {} Blocks", location.toString(), + response.getAddresses().size()); + + return response; + } + + + @ApiOperation( + value = "Returns a list of static address uids that are located in a bounding box with a given radius.", + notes = "Returns a list of static address uids that are located in a bounding box with a given radius." + ) + @RequestMapping(method = RequestMethod.GET, params = { LAT, LON, "distance" }) + @ResponseBody + public StaticAddressesUidResponse staticAddressesByBoundingBox(@RequestParam(LAT) BigDecimal latitude, + @RequestParam(LON) BigDecimal longitude, + @RequestParam("distance") Double distance) { + + GeoLocation location = new GeoLocation(latitude, longitude); + + StaticAddressesUidResponse response = new StaticAddressesUidResponse( + staticAddressDtoService.getStaticAddressByBoundingBox(location, distance)); + + LOG.info("API: Responding with {} items to boundingbox-request for geolocation {} with {} distance", + response.getUids().size(), location.toString(), distance); + + response.add(linkTo(methodOn(getClass()).staticAddressesByBoundingBox(latitude, longitude, distance)) + .withSelfRel()); + + return response; + } +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressDtoService.java b/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressDtoService.java new file mode 100644 index 00000000..60c07c90 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressDtoService.java @@ -0,0 +1,59 @@ +package net.contargo.iris.address.staticsearch.dto; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.dto.AddressDto; +import net.contargo.iris.address.dto.AddressListDto; + +import java.math.BigInteger; + +import java.util.List; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public interface StaticAddressDtoService { + + /** + * Finds all {@link AddressDto} of static addresses by the given parameters postalCode, city and country. + * + * @param postalCode String + * @param city String + * @param country String + * + * @return List of {@link AddressDto}s. + */ + List getAddressesByDetails(String postalCode, String city, String country); + + + /** + * Finds all {@link AddressDto} of static addresses. + * + * @return a list of all {@link AddressDto} of static addresses. + */ + List getAll(); + + + /** + * Returns one {@link net.contargo.iris.address.staticsearch.StaticAddress} wrapped in a list of lists for + * compatibility with the client and consistency to the other interfaces, that deliver actual lists of address + * lists. The static address is the one matching to the given Geolocation. + * + * @param location + * + * @return static address wrapped in a list of lists. + */ + List getStaticAddressByGeolocation(GeoLocation location); + + + /** + * Retrieves a list of static address uids that are located in a bounding box with radius {@code km} around + * {@code geoLocation}. + * + * @param location the geolocation at the bounding box's center + * @param distance the bounding box's radius + * + * @return a list of static address uids + */ + List getStaticAddressByBoundingBox(GeoLocation location, Double distance); +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressDtoServiceImpl.java b/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressDtoServiceImpl.java new file mode 100644 index 00000000..e018f85e --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressDtoServiceImpl.java @@ -0,0 +1,86 @@ +package net.contargo.iris.address.staticsearch.dto; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.AddressList; +import net.contargo.iris.address.dto.AddressDto; +import net.contargo.iris.address.dto.AddressListDto; +import net.contargo.iris.address.staticsearch.StaticAddress; +import net.contargo.iris.address.staticsearch.service.StaticAddressService; + +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public class StaticAddressDtoServiceImpl implements StaticAddressDtoService { + + private final StaticAddressService staticAddressService; + + public StaticAddressDtoServiceImpl(StaticAddressService staticAddressService) { + + this.staticAddressService = staticAddressService; + } + + @Override + public List getAddressesByDetails(String postalCode, String city, String country) { + + List addressList = staticAddressService.getAddressesByDetails(postalCode, city, country); + + return convertStaticAddressListToDTOList(addressList); + } + + + @Override + public List getAll() { + + List staticAddresses = staticAddressService.getAll(); + + return convertStaticAddressListToDTOList(staticAddresses); + } + + + private List convertStaticAddressListToDTOList(List addressList) { + + List addressDtoList = new ArrayList<>(); + + for (StaticAddress staticAddress : addressList) { + addressDtoList.add(new AddressDto(staticAddress.toAddress())); + } + + return addressDtoList; + } + + + @Override + public List getStaticAddressByGeolocation(GeoLocation location) { + + List addressListList = staticAddressService.getAddressListListForGeolocation(location); + + List addressListDtoList = new ArrayList<>(); + + for (AddressList addressList : addressListList) { + addressListDtoList.add(new AddressListDto(addressList)); // NOSONAR + } + + return addressListDtoList; + } + + + @Override + public List getStaticAddressByBoundingBox(GeoLocation location, Double distance) { + + List staticAddresses = staticAddressService.getAddressesInBoundingBox(location, distance); + + List result = new ArrayList<>(); + + for (StaticAddress staticAddress : staticAddresses) { + result.add(staticAddress.getUniqueId()); + } + + return result; + } +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressesResponse.java b/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressesResponse.java new file mode 100644 index 00000000..7a1b3fc9 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressesResponse.java @@ -0,0 +1,27 @@ +package net.contargo.iris.address.staticsearch.dto; + +import net.contargo.iris.address.dto.AddressDto; + +import org.springframework.hateoas.ResourceSupport; + +import java.util.List; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public class StaticAddressesResponse extends ResourceSupport { + + private List addressDtoList; + + public List getAddressDtoList() { + + return addressDtoList; + } + + + public void setAddressDtoList(List addressDtoList) { + + this.addressDtoList = addressDtoList; + } +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressesUidResponse.java b/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressesUidResponse.java new file mode 100644 index 00000000..f2efe4e5 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/dto/StaticAddressesUidResponse.java @@ -0,0 +1,32 @@ +package net.contargo.iris.address.staticsearch.dto; + +import org.springframework.hateoas.ResourceSupport; + +import java.math.BigInteger; + +import java.util.List; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public class StaticAddressesUidResponse extends ResourceSupport { + + private List uids; + + public StaticAddressesUidResponse(List staticAddressByBoundingBox) { + + uids = staticAddressByBoundingBox; + } + + public List getUids() { + + return uids; + } + + + public void setUids(List uids) { + + this.uids = uids; + } +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/persistence/StaticAddressRepository.java b/src/main/java/net/contargo/iris/address/staticsearch/persistence/StaticAddressRepository.java new file mode 100644 index 00000000..5de00242 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/persistence/StaticAddressRepository.java @@ -0,0 +1,90 @@ +package net.contargo.iris.address.staticsearch.persistence; + +import net.contargo.iris.address.staticsearch.StaticAddress; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import java.util.List; + + +/** + * Repository that provides access to data of {@link StaticAddress}. + * + * @author Aljona Murygina - murygina@synyx.de + */ +public interface StaticAddressRepository extends JpaRepository { + + String SELECT_FROM_STATICADDRESS_A = "SELECT a FROM StaticAddress a "; + String ORDER_BY_CITY_SUBURB_POSTALCODE = "ORDER BY a.cityNormalized, a.suburbNormalized, a.postalcode"; + + @Query( + SELECT_FROM_STATICADDRESS_A + + "WHERE a.postalcode = ?1 " + + "AND (a.cityNormalized LIKE ?2 OR a.suburbNormalized LIKE ?2) " + ORDER_BY_CITY_SUBURB_POSTALCODE + ) + List findByPostalCodeAndCity(String postalCode, String city); + + + @Query( + SELECT_FROM_STATICADDRESS_A + + "WHERE a.postalcode = ?1 " + + "OR a.cityNormalized LIKE ?2 " + + "OR a.suburbNormalized LIKE ?2 " + ORDER_BY_CITY_SUBURB_POSTALCODE + ) + List findByPostalCodeOrCity(String postalCode, String city); + + + @Query( + SELECT_FROM_STATICADDRESS_A + + "WHERE a.country = ?3 " + + "AND a.postalcode = ?1 " + + "AND (a.cityNormalized LIKE CONCAT('%', ?2, '%') OR a.suburbNormalized LIKE CONCAT('%', ?2, '%')) " + + ORDER_BY_CITY_SUBURB_POSTALCODE + ) + List findByCountryAndPostalCodeAndCity(String postalCode, String city, String country); + + + @Query( + SELECT_FROM_STATICADDRESS_A + + "WHERE a.country = ?3 " + + "AND ((a.postalcode = ?1 AND a.postalcode <> '') " + + "OR (a.cityNormalized LIKE ?2 AND a.cityNormalized <> '') " + + "OR (a.suburbNormalized LIKE ?2 AND a.suburbNormalized <> '')) " + ORDER_BY_CITY_SUBURB_POSTALCODE + ) + List findByCountryAndPostalCodeOrCity(String postalCode, String city, String country); + + + @Query( + SELECT_FROM_STATICADDRESS_A + + "WHERE (a.latitude between ?1 AND ?2) " + + "AND (a.longitude between ?3 AND ?4) " + ORDER_BY_CITY_SUBURB_POSTALCODE + ) + List findByBoundingBox(BigDecimal fromLatitude, BigDecimal toLatitude, BigDecimal fromLongitude, + BigDecimal toLongitude); + + + @Query( + SELECT_FROM_STATICADDRESS_A + + "WHERE (a.hashKey is null OR a.hashKey = '') " + ) + Page findMissingHashKeys(Pageable pageable); + + + List findByCityNormalizedAndSuburbNormalizedAndPostalcode(String city, String suburb, + String postalcode); + + + StaticAddress findByLatitudeAndLongitude(BigDecimal latitude, BigDecimal longitude); + + + StaticAddress findByUniqueId(BigInteger uniqueId); + + + List findByPostalcode(String postalCode); +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressCoordinatesDuplicationException.java b/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressCoordinatesDuplicationException.java new file mode 100644 index 00000000..8da91efd --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressCoordinatesDuplicationException.java @@ -0,0 +1,10 @@ +package net.contargo.iris.address.staticsearch.service; + +/** + * Is thrown when new StaticAddresses are putted in the db which, are duplicates (coordinates) to others. + * + * @author Michael Herbold - herbold@synyx.de + */ + +public class StaticAddressCoordinatesDuplicationException extends RuntimeException { +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressDuplicationException.java b/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressDuplicationException.java new file mode 100644 index 00000000..7f04019d --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressDuplicationException.java @@ -0,0 +1,10 @@ +package net.contargo.iris.address.staticsearch.service; + +/** + * Is thrown when new StaticAddresses are putted in the db which, are duplicates to others. + * + * @author David Schilling - schilling@synyx.de + */ + +public class StaticAddressDuplicationException extends RuntimeException { +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressService.java b/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressService.java new file mode 100644 index 00000000..ffa09f4e --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressService.java @@ -0,0 +1,123 @@ +package net.contargo.iris.address.staticsearch.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.AddressList; +import net.contargo.iris.address.staticsearch.StaticAddress; + +import java.util.List; + + +/** + * Provides service methods for {@link StaticAddress} entities. + * + * @author Aljona Murygina - murygina@synyx.de + */ +public interface StaticAddressService { + + /** + * Finds all {@link net.contargo.iris.address.Address}es by the given parameters postalCode, city and country. + * + * @param postalCode String + * @param city String + * @param country String + * + * @return {@link AddressList} + */ + AddressList findAddresses(String postalCode, String city, String country); + + + /** + * Finds all {@link StaticAddress} by the given parameters postalCode, city and country. The procedure of this + * method is following (step to next procedure if result list is empty): 1. Execute an AND search, i.e. find + * addresses by postal code AND city 2. Execute an OR search, i.e. find addresses by postal code OR city 3. Execute + * a split search, i.e. if the city string is "Neustadt an der Weinstrasse" and nothing is found for that then + * execute search for "Neustadt" + * + * @param postalCode String + * @param city String + * @param country String + * + * @return List of {@link StaticAddress}es. + */ + List getAddressesByDetailsWithFallbacks(String postalCode, String city, String country); + + + List getAddressesByDetails(String postalCode, String city, String country); + + + /** + * Finds an Address at the exact given location or null. + * + * @param loc + * + * @return + */ + StaticAddress getForLocation(GeoLocation loc); + + + /** + * @param staticAddressId + * + * @return + */ + StaticAddress findbyId(Long staticAddressId); + + + /** + * saves the staticAddress in the Database. + * + * @param staticAddress the staticAddress to save in the database + * + * @return staticAddress which was saved in the database + * + * @throws StaticAddressDuplicationException if duplicate was found. + */ + StaticAddress saveStaticAddress(StaticAddress staticAddress); + + + /** + * Finds all {@link StaticAddress}. + * + * @return a list of all {@link StaticAddress}. + */ + List getAll(); + + + /** + * @param staticAddressId + * + * @return The _one_ Address for the given static address ID. For consistent processing on client side it is + * wrapped in a list of AddressLists. + */ + List getAddressListListForStaticAddressId(Long staticAddressId); + + + /** + * @param location + * + * @return The _one_ Address for the given geoLocation. For consistent processing on client side it is wrapped in a + * list of AddressLists. + */ + List getAddressListListForGeolocation(GeoLocation location); + + + void normalizeFields(StaticAddress staticAddress); + + + /** + * looks for database entries with missing hashkeys. generates and saves the hashkeys. + */ + void fillMissingHashKeys(); + + + /** + * Retrieves a list of static addresses that are located in a bounding box with radius {@code km} around + * {@code geoLocation}. + * + * @param geoLocation the geolocation at the bounding box's center + * @param km the bounding box's radius + * + * @return a list of static addresses + */ + List getAddressesInBoundingBox(GeoLocation geoLocation, Double km); +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressServiceImpl.java b/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressServiceImpl.java new file mode 100644 index 00000000..932fcb71 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/service/StaticAddressServiceImpl.java @@ -0,0 +1,497 @@ +package net.contargo.iris.address.staticsearch.service; + +import net.contargo.iris.BoundingBox; +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.Address; +import net.contargo.iris.address.AddressList; +import net.contargo.iris.address.staticsearch.StaticAddress; +import net.contargo.iris.address.staticsearch.persistence.StaticAddressRepository; +import net.contargo.iris.normalizer.NormalizerServiceImpl; +import net.contargo.iris.sequence.service.SequenceService; + +import org.slf4j.Logger; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +import org.springframework.transaction.annotation.Transactional; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.lang.invoke.MethodHandles; + +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import javax.persistence.Entity; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Implementation of {@link StaticAddressService}. + * + * @author Aljona Murygina - murygina@synyx.de + */ +@Transactional +public class StaticAddressServiceImpl implements StaticAddressService { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + private static final String WILDCARD = "%"; + private static final int PAGE_SIZE = 50; + + private final StaticAddressRepository repository; + + private final SequenceService uniqueIdSequenceService; + + private final NormalizerServiceImpl normalizerService; + + public StaticAddressServiceImpl(StaticAddressRepository repository, SequenceService uniqueIdSequenceService, + NormalizerServiceImpl normalizerService) { + + this.repository = repository; + this.uniqueIdSequenceService = uniqueIdSequenceService; + this.normalizerService = normalizerService; + } + + /** + * Finds all {@link Address}es by the given parameters postalCode, city and country. The procedure of this method is + * following (step to next procedure if result list is empty): 1. Execute an AND search, i.e. find addresses by + * postal code AND city 2. Execute an OR search, i.e. find addresses by postal code OR city 3. Execute a split + * search, i.e. if the city string is "Neustadt an der Weinstrasse" and nothing is found for that then execute + * search for "Neustadt" + * + * @param postalCode String + * @param city String + * @param country String + * + * @return {@link AddressList} + */ + @Override + public AddressList findAddresses(String postalCode, String city, String country) { + + List staticAddresses = getAddressesByDetailsWithFallbacks(postalCode, city, country); + + List
addresses = staticAddresses.stream().map(StaticAddress::toAddress).collect(Collectors.toList()); + + return new AddressList("City and Suburb Results", addresses); + } + + + @Override + @Transactional(readOnly = true) + public List getAddressesByDetailsWithFallbacks(String postalCode, String city, String country) { + + String normalizedCity = normalizerService.normalize(city); + + List staticAddresses = executeANDSearch(postalCode, normalizedCity, country); + + if (staticAddresses.isEmpty()) { + // fallback to OR search + staticAddresses = executeORSearch(postalCode, normalizedCity, country); + } + + if (staticAddresses.isEmpty() && city != null) { + // fallback to split search + staticAddresses = executeSplitSearch(postalCode, city, country); + } + + return staticAddresses; + } + + + @Override + public List getAddressesByDetails(String postalCode, String city, String country) { + + String normalizedCity = normalizerService.normalize(city); + + return repository.findByCountryAndPostalCodeAndCity(postalCode, normalizedCity, country); + } + + + @Override + @Transactional(readOnly = true) + public StaticAddress getForLocation(GeoLocation loc) { + + return repository.findByLatitudeAndLongitude(loc.getLatitude(), loc.getLongitude()); + } + + + @Override + @Transactional(readOnly = true) + public StaticAddress findbyId(Long staticAddressId) { + + return repository.findOne(staticAddressId); + } + + + @Override + public synchronized StaticAddress saveStaticAddress(StaticAddress staticAddress) { + + setEmptyValues(staticAddress); + + normalizeFields(staticAddress); + + if (staticAddress.getId() == null) { + return saveNewStaticAddress(staticAddress); + } else { + return updateStaticAddress(staticAddress); + } + } + + + @Override + public List getAll() { + + return repository.findAll(); + } + + + /** + * @param staticAddressId + * + * @return The _one_ Address for the given static address ID. For consistent processing on client side it is + * wrapped in a list of AddressLists. + */ + @Override + public List getAddressListListForStaticAddressId(Long staticAddressId) { + + StaticAddress staticAddress = findbyId(staticAddressId); + + if (staticAddress == null) { + return Collections.emptyList(); + } + + AddressList staticAddressList = new AddressList("Result ", Arrays.asList(staticAddress.toAddress())); + + return Arrays.asList(staticAddressList); + } + + + /** + * @param location + * + * @return The _one_ Address for the given geoLocation. For consistent processing on client side it is wrapped in a + * list of AddressLists. + */ + @Override + public List getAddressListListForGeolocation(GeoLocation location) { + + StaticAddress staticAddress = getForLocation(location); + + if (staticAddress == null) { + return Collections.emptyList(); + } + + AddressList staticAddressList = new AddressList("Result ", Arrays.asList(staticAddress.toAddress())); + + return Arrays.asList(staticAddressList); + } + + + private void setEmptyValues(StaticAddress staticAddress) { + + if (staticAddress.getCity() == null) { + staticAddress.setCity(""); + } + + if (staticAddress.getPostalcode() == null) { + staticAddress.setPostalcode(""); + } + + if (staticAddress.getSuburb() == null) { + staticAddress.setSuburb(""); + } + } + + + @Override + public void normalizeFields(StaticAddress staticAddress) { + + if (staticAddress.getCity() != null) { + staticAddress.setCityNormalized(normalizerService.normalize(staticAddress.getCity())); + } + + if (staticAddress.getSuburb() != null) { + staticAddress.setSuburbNormalized(normalizerService.normalize(staticAddress.getSuburb())); + } + } + + + @Override + public void fillMissingHashKeys() { + + LOG.info("Starting to fill the static address hashkeys with " + PAGE_SIZE + " items per page"); + + int currentPage = 0; + int totalPages = getTotalPages(currentPage, PAGE_SIZE); + + boolean hasElements = true; + + while (hasElements) { + List addressesOfCurrentPage = getAddresses(0, PAGE_SIZE); + + // change state + hasElements = !addressesOfCurrentPage.isEmpty(); + + if (hasElements) { + LOG.info(String.format("Processing page %d of %d", currentPage + 1, totalPages)); + + for (StaticAddress staticAddress : addressesOfCurrentPage) { + staticAddress.setUniqueId(staticAddress.getUniqueId()); + repository.save(staticAddress); + } + + currentPage = currentPage + 1; + } + } + + LOG.info("Finished filling the hashkeys"); + } + + + @Override + public List getAddressesInBoundingBox(GeoLocation geoLocation, Double km) { + + Assert.notNull(geoLocation); + Assert.notNull(km); + + BoundingBox box = geoLocation.getBoundingBox(km); + GeoLocation lowerLeft = box.getLowerLeft(); + GeoLocation upperRight = box.getUpperRight(); + + return repository.findByBoundingBox(lowerLeft.getLatitude(), upperRight.getLatitude(), lowerLeft.getLongitude(), + upperRight.getLongitude()); + } + + + private StaticAddress updateStaticAddress(StaticAddress staticAddress) { + + StaticAddress staticAddressFromDb = repository.findOne(staticAddress.getId()); + + boolean addressParametersDifferent = staticAddressFromDb.areAddressParametersDifferent(staticAddress); + + boolean coordinatesDifferent = staticAddressFromDb.areLatitudeAndLongitudeDifferent(staticAddress); + + if (addressParametersDifferent && checkDuplicateAddressParameters(staticAddress)) { + throw new StaticAddressDuplicationException(); + } + + if (coordinatesDifferent && checkOnDuplicateCoordinates(staticAddressFromDb, staticAddress)) { + throw new StaticAddressCoordinatesDuplicationException(); + } + + return repository.save(staticAddress); + } + + + private StaticAddress saveNewStaticAddress(StaticAddress staticAddress) { + + if (staticAddress.getUniqueId() == null) { + staticAddress.setUniqueId(determineUniqueId()); + } + + if (checkDuplicateAddressParameters(staticAddress)) { + throw new StaticAddressDuplicationException(); + } + + if (checkOnDuplicateCoordinates(staticAddress)) { + throw new StaticAddressCoordinatesDuplicationException(); + } + + return repository.save(staticAddress); + } + + + BigInteger determineUniqueId() { + + String entityName = StaticAddress.class.getAnnotation(Entity.class).name(); + BigInteger nextUniqueId = uniqueIdSequenceService.getNextId(entityName); + boolean isUniqueIdAlreadyAssigned = isUniqueIdAlreadyAssigned(nextUniqueId); + + while (isUniqueIdAlreadyAssigned) { + // In this loop we increment the ID by ourselves to avoid write-accesses to the DB for performance + LOG.warn("StaticAddress uniqueId {} already assigned - trying next uniqueId", nextUniqueId); + nextUniqueId = nextUniqueId.add(BigInteger.ONE); + + if (!isUniqueIdAlreadyAssigned(nextUniqueId)) { + isUniqueIdAlreadyAssigned = false; + uniqueIdSequenceService.setNextId(entityName, nextUniqueId); + } + } + + return nextUniqueId; + } + + + private boolean isUniqueIdAlreadyAssigned(BigInteger uniqueId) { + + return repository.findByUniqueId(uniqueId) != null; + } + + + private boolean checkOnDuplicateCoordinates(StaticAddress staticAddressFromDb, StaticAddress staticAddress) { + + // compare coordinates + if (null != staticAddressFromDb && staticAddressFromDb.equals(staticAddress)) { + return false; + } + + StaticAddress addressFromDb = repository.findByLatitudeAndLongitude(staticAddress.getLatitude(), + staticAddress.getLongitude()); + + return null != addressFromDb; + } + + + private boolean checkOnDuplicateCoordinates(StaticAddress staticAddress) { + + return this.checkOnDuplicateCoordinates(null, staticAddress); + } + + + private boolean checkDuplicateAddressParameters(StaticAddress staticAddress) { + + List staticAddresses = repository.findByCityNormalizedAndSuburbNormalizedAndPostalcode( + staticAddress.getCityNormalized(), staticAddress.getSuburbNormalized(), staticAddress.getPostalcode()); + + return !staticAddresses.isEmpty(); + } + + + /** + * Similar to method executeORSearch, only using other repository methods. + */ + private List executeANDSearch(String postalCode, String city, String country) { + + List addresses = new ArrayList<>(); + + if (StringUtils.hasText(country)) { + addresses = repository.findByCountryAndPostalCodeAndCity(postalCode, getParameterWithWildcard(city), + country); + } else if (StringUtils.hasText(city)) { + addresses = repository.findByPostalCodeAndCity(postalCode, getParameterWithWildcard(city)); + } else { + repository.findByPostalcode(postalCode); + } + + return addresses; + } + + + /** + * Similar to method executeANDSearch, only using other repository methods. + */ + private List executeORSearch(String postalCode, String city, String country) { + + List addresses; + + if (StringUtils.hasText(country)) { + addresses = repository.findByCountryAndPostalCodeOrCity(postalCode, getParameterWithWildcard(city), + country); + } else if (StringUtils.hasText(city)) { + addresses = repository.findByPostalCodeOrCity(postalCode, getParameterWithWildcard(city)); + } else { + addresses = repository.findByPostalcode(postalCode); + } + + return addresses; + } + + + /** + * Adds wildcard to String if it is not empty and returns the new String. + * + * @param param + * + * @return new String with wildcard + */ + private String getParameterWithWildcard(String param) { + + // rule for wildcard city: "city%" + if (StringUtils.hasText(param)) { + return param + WILDCARD; + } + + return param; + } + + + /** + * This is a fallback method if neither executeANDSearch nor executeORSearch have a result. The city string is split + * on whitespaces. The current implementation executes the search only for first element of the split string. + * + * @param postalCode + * @param city + * @param country + * + * @return {@link java.util.List} of {@link StaticAddress} matching the given search parameters + */ + private List executeSplitSearch(String postalCode, String city, String country) { + + Assert.notNull(city); + + String[] singleSearchParameters = city.split(" "); + + String firstString = normalizerService.normalize(singleSearchParameters[0]); + List addresses = executeANDSearch(postalCode, firstString, country); + + if (addresses.isEmpty()) { + addresses = executeORSearch(postalCode, firstString, country); + } + + return addresses; + } + + + /** + * Retrieves total amount of pages, according to pagesize. + * + * @param currentPage + * @param pageSize + * + * @return + */ + private int getTotalPages(int currentPage, int pageSize) { + + Pageable pageable = new PageRequest(currentPage, pageSize); + + return getAllPagesForEmptyItems(pageable).getTotalPages(); + } + + + /** + * Retreive only empty pages from repository.. + * + * @param pageable + * + * @return + */ + private Page getAllPagesForEmptyItems(Pageable pageable) { + + return repository.findMissingHashKeys(pageable); + } + + + /** + * Get pageable addresses according to the given page size. + * + * @param startPage + * @param pageSize + * + * @return + */ + private List getAddresses(int startPage, int pageSize) { + + Pageable pageable = new PageRequest(startPage, pageSize); + + return getAllPagesForEmptyItems(pageable).getContent(); + } +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/web/StaticAddressController.java b/src/main/java/net/contargo/iris/address/staticsearch/web/StaticAddressController.java new file mode 100644 index 00000000..26b143aa --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/web/StaticAddressController.java @@ -0,0 +1,161 @@ +package net.contargo.iris.address.staticsearch.web; + +import net.contargo.iris.Message; +import net.contargo.iris.address.staticsearch.StaticAddress; +import net.contargo.iris.address.staticsearch.service.StaticAddressCoordinatesDuplicationException; +import net.contargo.iris.address.staticsearch.service.StaticAddressDuplicationException; +import net.contargo.iris.address.staticsearch.service.StaticAddressService; +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.sequence.service.UniqueIdSequenceServiceException; + +import org.slf4j.Logger; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.ui.Model; + +import org.springframework.validation.BindingResult; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.invoke.MethodHandles; + +import java.util.List; + +import javax.validation.Valid; + +import static net.contargo.iris.Message.error; +import static net.contargo.iris.Message.success; +import static net.contargo.iris.api.AbstractController.SLASH; +import static net.contargo.iris.api.AbstractController.STATIC_ADDRESSES; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Controller for the web API of {@link net.contargo.iris.address.staticsearch.StaticAddress}s. + * + * @author Michael Herbold - herbold@synyx.de + * @author Arnold Franke - franke@synyx.de + * @author Jörg Alberto Hoffmann - hoffmann@synyx.de + */ +@Controller +@RequestMapping(SLASH + STATIC_ADDRESSES) +public class StaticAddressController extends AbstractController { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private static final String CONTROLLER_CONTEXT = "staticAddressManagement" + SLASH; + + private static final String STATIC_ADDRESS_REQUEST_BEAN = "request"; + private static final String ENTITY_ATTRIBUTE = "staticAddress"; + private static final String ENTITYS_ATTRIBUTE = "staticAddresses"; + + private static final String ENTITY_VIEW = "staticaddresses"; + private static final String ENTITY_FORM_VIEW = "staticaddressForm"; + + private static final Message SAVE_SUCCESS_MESSAGE = success("staticaddress.save.success"); + private static final Message UPDATE_SUCCESS_MESSAGE = success("staticaddress.update.success"); + private static final Message DUPLICATION_ERROR_MESSAGE = error("staticaddress.error.duplicate"); + private static final Message DUPLICATION_GEOCOORDINATES_ERROR_MESSAGE = error( + "staticaddress.error.coordinates.duplicate"); + + private final StaticAddressService staticAddressService; + + @Autowired + public StaticAddressController(StaticAddressService staticAddressService) { + + this.staticAddressService = staticAddressService; + } + + @RequestMapping(method = RequestMethod.GET) + public String getByDetails(Model model, @ModelAttribute StaticAddressRequest staticAddressRequest) { + + if (!staticAddressRequest.isEmpty()) { + List staticAddressList = staticAddressService.getAddressesByDetailsWithFallbacks( + staticAddressRequest.getPostalcode(), staticAddressRequest.getCity(), null); + + model.addAttribute(ENTITYS_ATTRIBUTE, staticAddressList); + } + + model.addAttribute(STATIC_ADDRESS_REQUEST_BEAN, staticAddressRequest); + + return CONTROLLER_CONTEXT + ENTITY_VIEW; + } + + + @RequestMapping(value = SLASH + ID_PARAM, method = RequestMethod.GET) + public String getStaticAddress(Model model, @PathVariable Long id) { + + model.addAttribute(ENTITY_ATTRIBUTE, staticAddressService.findbyId(id)); + + return CONTROLLER_CONTEXT + ENTITY_FORM_VIEW; + } + + + @RequestMapping(value = SLASH + "new", method = RequestMethod.GET) + public String prepareForCreate(Model model) { + + model.addAttribute(ENTITY_ATTRIBUTE, new StaticAddress()); + + return CONTROLLER_CONTEXT + ENTITY_FORM_VIEW; + } + + + @ModelAttribute("staticAddress") + public void prepareSaveStaticAddress(StaticAddress staticAddress) { + + staticAddressService.normalizeFields(staticAddress); + } + + + @RequestMapping(value = SLASH, method = RequestMethod.POST) + public String saveStaticAddress(@Valid @ModelAttribute StaticAddress staticAddress, BindingResult result, + Model model) { + + return saveOrUpdateStaticAddress(staticAddress, result, model, SAVE_SUCCESS_MESSAGE); + } + + + @RequestMapping(value = SLASH + ID_PARAM, method = RequestMethod.PUT) + public String updateStaticAddress(@Valid @ModelAttribute StaticAddress staticAddress, BindingResult result, + Model model) { + + return saveOrUpdateStaticAddress(staticAddress, result, model, UPDATE_SUCCESS_MESSAGE); + } + + + private String saveOrUpdateStaticAddress(StaticAddress staticAddress, BindingResult result, Model model, + Message message) { + + if (result.hasErrors()) { + model.addAttribute(ENTITY_ATTRIBUTE, staticAddress); + + return CONTROLLER_CONTEXT + ENTITY_FORM_VIEW; + } + + try { + StaticAddress savedStaticAddress = staticAddressService.saveStaticAddress(staticAddress); + + model.addAttribute(ENTITY_ATTRIBUTE, savedStaticAddress); + model.addAttribute(AbstractController.MESSAGE, message); + } catch (StaticAddressDuplicationException e) { + model.addAttribute(ENTITY_ATTRIBUTE, staticAddress); + model.addAttribute(AbstractController.MESSAGE, DUPLICATION_ERROR_MESSAGE); + } catch (StaticAddressCoordinatesDuplicationException e) { + model.addAttribute(ENTITY_ATTRIBUTE, staticAddress); + model.addAttribute(AbstractController.MESSAGE, DUPLICATION_GEOCOORDINATES_ERROR_MESSAGE); + } catch (UniqueIdSequenceServiceException e) { + model.addAttribute(ENTITY_ATTRIBUTE, staticAddress); + model.addAttribute(AbstractController.MESSAGE, UNIQUEID_ERROR_MESSAGE); + LOG.error(e.getMessage()); + } + + return CONTROLLER_CONTEXT + ENTITY_FORM_VIEW; + } +} diff --git a/src/main/java/net/contargo/iris/address/staticsearch/web/StaticAddressRequest.java b/src/main/java/net/contargo/iris/address/staticsearch/web/StaticAddressRequest.java new file mode 100644 index 00000000..ddefb072 --- /dev/null +++ b/src/main/java/net/contargo/iris/address/staticsearch/web/StaticAddressRequest.java @@ -0,0 +1,52 @@ +package net.contargo.iris.address.staticsearch.web; + +import org.springframework.util.StringUtils; + + +/** + * View bean to encapsulate search parameter for a static address. + * + * @author Michael Herbold - herbold@synyx.de + */ +class StaticAddressRequest { + + private String postalcode = null; + private String city = null; + + public String getPostalcode() { + + if (null != postalcode && postalcode.isEmpty()) { + postalcode = null; + } + + return postalcode; + } + + + public void setPostalcode(String postalcode) { + + this.postalcode = postalcode; + } + + + public String getCity() { + + if (null != city && city.isEmpty()) { + city = null; + } + + return city; + } + + + public void setCity(String city) { + + this.city = city; + } + + + public boolean isEmpty() { + + return StringUtils.isEmpty(postalcode) && StringUtils.isEmpty(city); + } +} diff --git a/src/main/java/net/contargo/iris/api/AbstractController.java b/src/main/java/net/contargo/iris/api/AbstractController.java new file mode 100644 index 00000000..589fa6ca --- /dev/null +++ b/src/main/java/net/contargo/iris/api/AbstractController.java @@ -0,0 +1,54 @@ +package net.contargo.iris.api; + +import net.contargo.iris.Message; + +import static net.contargo.iris.Message.error; + + +/** + * @author Vincent Potucek - potucek@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public abstract class AbstractController { + + public static final Message UNIQUEID_ERROR_MESSAGE = error("uniqueid.error.message"); + + // Domain specific constants + public static final String TERMINALS = "terminals"; + public static final String SEAPORTS = "seaports"; + public static final String ROUTE_TYPES = "routetypes"; + public static final String STATIC_ADDRESSES = "staticaddresses"; + public static final String CONNECTIONS = "connections"; + public static final String LOGIN = "login"; + public static final String ROUTE_DETAILS = "routedetails"; + public static final String OSM_ADDRESSES = "osmaddresses"; + public static final String COUNTRIES = "countries"; + public static final String REVERSE_GEOCODE = "reversegeocode"; + public static final String ADDRESSES = "addresses"; + public static final String PLACES = "places"; + public static final String GEOCODES = "geocodes"; + public static final String SIMPLE_GEOCODES = "simplegeocodes"; + public static final String TRIANGLE = "triangle"; + public static final String TRIANGLE_VIEW = "routing/triangle"; + + // parameter constants + public static final String ID_PARAM = "{id}"; + public static final String ID = "id"; + public static final String PARAM_LATITUDE = "{lat}"; + public static final String PARAM_LONGITUDE = "{lon}"; + + // character constants + public static final String SLASH = "/"; + public static final String COLON = ":"; + public static final String STAR = "*"; + + // navigation constants + public static final String FORM = "form"; + public static final String MESSAGE = "message"; + public static final String REDIRECT = "redirect:"; + public static final String RESPONSE = "response"; + public static final String WEB = "web"; + public static final String LINK_REF_ROOT = "root"; + public static final String INDEX = "index"; + public static final String WEBAPI_ROOT_URL = SLASH + WEB + SLASH; +} diff --git a/src/main/java/net/contargo/iris/api/NotFoundException.java b/src/main/java/net/contargo/iris/api/NotFoundException.java new file mode 100644 index 00000000..429d96a1 --- /dev/null +++ b/src/main/java/net/contargo/iris/api/NotFoundException.java @@ -0,0 +1,14 @@ +package net.contargo.iris.api; + +/** + * Should be thrown when an entity cannot be found. Will be mapped to an HTTP 404 error. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public class NotFoundException extends RuntimeException { + + public NotFoundException(String message) { + + super(message); + } +} diff --git a/src/main/java/net/contargo/iris/api/PublicAPIExceptionHandler.java b/src/main/java/net/contargo/iris/api/PublicAPIExceptionHandler.java new file mode 100644 index 00000000..6ab89315 --- /dev/null +++ b/src/main/java/net/contargo/iris/api/PublicAPIExceptionHandler.java @@ -0,0 +1,98 @@ +package net.contargo.iris.api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +/** + * This ExceptionHandler handles thrown Exceptions of our public API and responds with Exception specific HTTP status + * codes. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +public class PublicAPIExceptionHandler implements HandlerExceptionResolver { + + private static final Logger LOG = LoggerFactory.getLogger(PublicAPIExceptionHandler.class); + + @Override + public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, + Exception ex) { + + ModelAndView modelAndView = new ModelAndView(); + + LOG.error("REST-API Exception: " + ex.getMessage(), ex); + + try { + if (ex instanceof IllegalArgumentException) { + modelAndView = handleIllegalArgumentException(response, (IllegalArgumentException) ex); + } else if (ex instanceof IllegalStateException) { + modelAndView = handleIllegalStateException(response, (IllegalStateException) ex); + } else if (ex instanceof HttpClientErrorException) { + modelAndView = handleHttpClientErrorException(response, (HttpClientErrorException) ex); + } else if (ex instanceof NotFoundException) { + modelAndView = handleNotFoundException(response, (NotFoundException) ex); + } else { + modelAndView = handleGenericException(response, ex); + } + } catch (IOException handlerException) { + LOG.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException); + } + + return modelAndView; + } + + + private ModelAndView handleIllegalArgumentException(HttpServletResponse response, IllegalArgumentException ex) + throws IOException { + + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad Request. " + ex.getMessage()); + + return new ModelAndView(); + } + + + private ModelAndView handleIllegalStateException(HttpServletResponse response, IllegalStateException ex) + throws IOException { + + response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Bad request. " + ex.getMessage()); + + return new ModelAndView(); + } + + + private ModelAndView handleHttpClientErrorException(HttpServletResponse response, HttpClientErrorException ex) + throws IOException { + + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, + "Service is temporary not available, please try again later. " + ex.getMessage()); + + return new ModelAndView(); + } + + + private ModelAndView handleGenericException(HttpServletResponse response, Exception ex) throws IOException { + + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal Server Error. " + ex.getMessage()); + + return new ModelAndView(); + } + + + private ModelAndView handleNotFoundException(HttpServletResponse response, NotFoundException ex) + throws IOException { + + response.sendError(HttpServletResponse.SC_NOT_FOUND, "Not found. " + ex.getMessage()); + + return new ModelAndView(); + } +} diff --git a/src/main/java/net/contargo/iris/api/WebExceptionHandler.java b/src/main/java/net/contargo/iris/api/WebExceptionHandler.java new file mode 100644 index 00000000..bc5656c3 --- /dev/null +++ b/src/main/java/net/contargo/iris/api/WebExceptionHandler.java @@ -0,0 +1,120 @@ +package net.contargo.iris.api; + +import net.contargo.iris.security.UserAuthenticationService; + +import org.joda.time.DateTime; + +import org.slf4j.Logger; + +import org.springframework.security.core.Authentication; + +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; + +import java.lang.invoke.MethodHandles; + +import java.util.Random; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * This ExceptionHandler generates detailed error reports if an unexpected error resp. Exception occurs. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public class WebExceptionHandler extends SimpleMappingExceptionResolver implements HandlerExceptionResolver { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + private static final int MAX_RANDOM_NUMBER = 9999; + + private final File basePath; + private final Random random = new Random(); + private final UserAuthenticationService userAuthenticationService; + + public WebExceptionHandler(File basePath, UserAuthenticationService userAuthenticationService) { + + this.basePath = basePath; + this.userAuthenticationService = userAuthenticationService; + + createDirectoryIfNotExists(basePath); + } + + String textualRepresentationOfCurrentUser() { + + Authentication currentUser = userAuthenticationService.getCurrentUser(); + + return currentUser == null ? "?" : String.format("user: %s%n", currentUser.getName()); + } + + + @Override + public ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, + Exception e) { + + // generate unique id for exception + DateTime now = new DateTime(); + String id = String.format("%s-%4d", now.toString("yyyy-MM-dd_HH-mm-ss"), random.nextInt(MAX_RANDOM_NUMBER)); + + request.setAttribute("exception_id", id); + + String user = textualRepresentationOfCurrentUser(); + + addExceptionStackTraceToRequest(request, e); + + File file = new File(basePath, id + "-report.txt"); + + try { + PrintWriter writer = new PrintWriter(file, "UTF-8"); + writer.append(String.format( + "id: %s%ndate: %s%n%s%n" + "requesturl: %s%ncontroller: %s%n%n%n" + + "stacktrace:%n", id, now.toString("dd.mm.yyyy HH:mm:ss"), user, + request.getRequestURL().toString(), handler.getClass().getName())); + + e.printStackTrace(writer); + writer.append("\n\n"); + + writer.close(); + } catch (FileNotFoundException fileNotFoundException) { + LOG.warn("File not found.", fileNotFoundException); + } catch (UnsupportedEncodingException unsupportedEncodingException) { + LOG.warn("Encoding not supported.", unsupportedEncodingException); + } + + LOG.error("A new error report was generated due to an exception. See " + file.getPath() + + " for further information.", e); + + return super.doResolveException(request, response, handler, e); + } + + + private void addExceptionStackTraceToRequest(HttpServletRequest request, Exception exception) { + + StringWriter writer = new StringWriter(); + exception.printStackTrace(new PrintWriter(writer)); + request.setAttribute("exception_trace", writer.toString()); + } + + + private void createDirectoryIfNotExists(File dir) { + + if (dir.exists() && !dir.isDirectory()) { + throw new IllegalStateException("Directory " + dir.getAbsolutePath() + + " exists and is a file (needed a directory)"); + } else if (!dir.exists() && !dir.mkdirs()) { + throw new IllegalStateException("Directory " + dir.getAbsolutePath() + + " can not be created"); + } + } +} diff --git a/src/main/java/net/contargo/iris/api/discover/DiscoverPublicApiController.java b/src/main/java/net/contargo/iris/api/discover/DiscoverPublicApiController.java new file mode 100644 index 00000000..33fefc88 --- /dev/null +++ b/src/main/java/net/contargo/iris/api/discover/DiscoverPublicApiController.java @@ -0,0 +1,135 @@ +package net.contargo.iris.api.discover; + +import com.mangofactory.swagger.annotations.ApiIgnore; + +import net.contargo.iris.address.api.AddressApiController; +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.connection.api.MainRunConnectionApiController; +import net.contargo.iris.container.ContainerType; +import net.contargo.iris.countries.api.CountriesApiController; +import net.contargo.iris.enricher.api.RouteEnricherApiController; +import net.contargo.iris.route.RouteCombo; +import net.contargo.iris.seaport.api.SeaportApiController; +import net.contargo.iris.terminal.api.TerminalApiController; + +import org.springframework.beans.factory.annotation.Value; + +import org.springframework.stereotype.Controller; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.lang.reflect.Method; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + + +/** + * Controller which presents all public API's as link so they can be used to move through the API. + * + * @author Marc Kannegiesser - kannegiesser@synyx.de + * @author David Schilling - schilling@synyx.de + */ +@Controller +@RequestMapping +@ApiIgnore +public class DiscoverPublicApiController extends AbstractController { + + static final String REL_COUNTRIES = COUNTRIES; + static final String REL_REVERSE_GEOCODE = REVERSE_GEOCODE; + static final String REL_GEOCODE = "geocode"; + static final String REL_TERMINALS = TERMINALS; + static final String REL_TERMINAL_EXAMPLE = "terminal (by uid)"; + static final String REL_SEAPORTS_OF_CONNECTIONS = "seaports (as part of connections)"; + static final String REL_SEAPORTS_OF_CONNECTIONS_FILTERED = "seaports (as part of connections, filtered)"; + static final String REL_SEAPORT_EXAMPLE = "seaport (by uid)"; + static final String REL_CONNECTIONS = "connections_url"; + static final String REL_SIMPLE_GEOCODES_EXAMPLE = "simplegeocodes_example"; + static final String REL_ROUTE_DETAILS_EXAMPLE = "route_details_example"; + static final String REL_OSM_ADDRESSES = OSM_ADDRESSES; + + private static final String ROOT_URL = SLASH; + private static final Double SEAPORTS_LAT = 49.0; + private static final Double SEAPORTS_LON = 8.41; + + private static final BigInteger SEAPORT_UID = new BigInteger("1301000000000001"); + private static final BigInteger TERMINAL_UID = new BigInteger("1301000000000001"); + + @Value(value = "${application.version}") + private String applicationVersion; + + @RequestMapping(value = ROOT_URL, method = RequestMethod.GET) + @ModelAttribute(RESPONSE) + public DiscoverResponse discover() throws NoSuchMethodException { + + DiscoverResponse discoverResponse = new DiscoverResponse(applicationVersion); + + // connections_url + discoverResponse.add(linkTo( + methodOn(MainRunConnectionApiController.class).getSeaportRoutes(SEAPORT_UID, SEAPORTS_LAT, SEAPORTS_LON, + true, ContainerType.TWENTY_LIGHT, false, RouteCombo.WATERWAY)).withRel(REL_CONNECTIONS)); + + // countries + discoverResponse.add(linkTo(CountriesApiController.class).withRel(REL_COUNTRIES)); + + // osmaddresses + discoverResponse.add(linkTo(AddressApiController.class).slash(OSM_ADDRESSES).slash( + "134631686?_=1381911583029").withRel(REL_OSM_ADDRESSES)); + + // reverse_geocode + Method method = AddressApiController.class.getMethod(AddressApiController.METHOD_ADDRESS_BY_GEOLOCATION, + BigDecimal.class, BigDecimal.class); + discoverResponse.add(linkTo(method, new BigDecimal("49.123"), new BigDecimal("8.12")).slash(".").withRel( + REL_REVERSE_GEOCODE)); + + // geocode + discoverResponse.add(linkTo(AddressApiController.class).slash(GEOCODES + "?city=Karlsruhe&postalcode=76137") + .withRel(REL_GEOCODE)); + + // seaport + discoverResponse.add(linkTo(methodOn(SeaportApiController.class).getSeaportById(SEAPORT_UID)).withRel( + REL_SEAPORT_EXAMPLE)); + discoverResponse.add(linkTo( + methodOn(MainRunConnectionApiController.class).getSeaportsInConnections(RouteCombo.ALL)).withRel( + REL_SEAPORTS_OF_CONNECTIONS)); + discoverResponse.add(linkTo( + methodOn(MainRunConnectionApiController.class).getSeaportsInConnections(RouteCombo.RAILWAY)).withRel( + REL_SEAPORTS_OF_CONNECTIONS_FILTERED)); + + // terminal (by uid) + discoverResponse.add(linkTo(methodOn(TerminalApiController.class).getTerminalByUid(TERMINAL_UID)).withRel( + REL_TERMINAL_EXAMPLE)); + + // terminals + discoverResponse.add(linkTo(methodOn(TerminalApiController.class).getTerminals()).withRel(REL_TERMINALS)); + + discoverResponse.add(linkTo(AddressApiController.class).slash( + "simplegeocodes?city=Karlsruhe&postalcode=76137").withRel(REL_SIMPLE_GEOCODES_EXAMPLE)); + + discoverResponse.add(linkTo(RouteEnricherApiController.class).slash( + "?data.parts[0].origin.longitude=4.3&data.parts[0].origin.latitude=51.36833" + + "&data.parts[0].destination.longitude=8.2852700000&data.parts[0].destination.latitude=49.0690300000" + + "&data.parts[0].routeType=BARGE&data.parts[0].containerType=TWENTY_LIGHT" + + "&data.parts[0].containerState=FULL" + + "&data.parts[1].origin.longitude=8.2852700000&data.parts[1].origin.latitude=49.0690300000" + + "&data.parts[1].destination.longitude=8.41&data.parts[1].destination.latitude=49.0" + + "&data.parts[1].routeType=TRUCK&data.parts[1].containerType=TWENTY_LIGHT" + + "&data.parts[1].containerState=FULL" + + "&data.parts[2].origin.longitude=8.41&data.parts[2].origin.latitude=49.0" + + "&data.parts[2].destination.longitude=8.2852700000&data.parts[2].destination.latitude=49.0690300000" + + "&data.parts[2].routeType=TRUCK&data.parts[2].containerType=TWENTY_LIGHT" + + "&data.parts[2].containerState=EMPTY" + + "&data.parts[3].origin.longitude=8.2852700000&data.parts[3].origin.latitude=49.0690300000" + + "&data.parts[3].destination.longitude=4.3&data.parts[3].destination.latitude=51.36833" + + "&data.parts[3].routeType=BARGE" + + "&data.parts[3].containerType=TWENTY_LIGHT&data.parts[3].containerState=EMPTY").withRel( + REL_ROUTE_DETAILS_EXAMPLE)); + + return discoverResponse; + } +} diff --git a/src/main/java/net/contargo/iris/api/discover/DiscoverResponse.java b/src/main/java/net/contargo/iris/api/discover/DiscoverResponse.java new file mode 100644 index 00000000..b4c942d5 --- /dev/null +++ b/src/main/java/net/contargo/iris/api/discover/DiscoverResponse.java @@ -0,0 +1,38 @@ +package net.contargo.iris.api.discover; + +import org.springframework.hateoas.ResourceSupport; + + +/** + * Response Object which is used in the {@link DiscoverPublicApiController} to return the current version of the + * application. + * + * @author Marc Kannegiesser - kannegiesser@synyx.de + * @author David Schilling - schilling@synyx.de + */ +class DiscoverResponse extends ResourceSupport { + + private String version; + + public DiscoverResponse(String applicationVersion) { + + this.version = applicationVersion; + } + + + public DiscoverResponse() { + + // Needed for Jackson Mapping + } + + public String getVersion() { + + return version; + } + + + public void setVersion(String version) { + + this.version = version; + } +} diff --git a/src/main/java/net/contargo/iris/api/discover/TestExplainView.java b/src/main/java/net/contargo/iris/api/discover/TestExplainView.java new file mode 100644 index 00000000..92255197 --- /dev/null +++ b/src/main/java/net/contargo/iris/api/discover/TestExplainView.java @@ -0,0 +1,173 @@ +package net.contargo.iris.api.discover; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; + +import net.contargo.iris.security.UserAuthenticationService; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; + +import org.springframework.context.ApplicationContextAware; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.ResourceSupport; + +import org.springframework.web.servlet.view.json.MappingJackson2JsonView; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import java.net.URLDecoder; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import static org.apache.commons.lang.CharEncoding.UTF_8; + + +/** + * @author Marc Kannegiesser - kannegiesser@synyx.de + * @author David Schilling - schilling@synyx.de + */ +public class TestExplainView extends MappingJackson2JsonView implements ApplicationContextAware { + + private final ObjectMapper objectMapper; + + @Value(value = "${application.version}") + private String applicationVersion; + + private final UserAuthenticationService userAuthenticationService; + + @Autowired + public TestExplainView(UserAuthenticationService userAuthenticationService, ObjectMapper objectMapper) { + + setContentType("text/html"); + this.userAuthenticationService = userAuthenticationService; + this.objectMapper = objectMapper; + } + + @Override + protected void renderMergedOutputModel(Map model, HttpServletRequest request, + HttpServletResponse response) throws IOException { + + @SuppressWarnings("unchecked") + Map value = (Map) filterModel(model); + + StringBuilder builder = new StringBuilder(); + + addGreetingSection(builder, request); + + addFormatsSection(request, builder); + + addLinkSection(builder, value); + + addJSONPrettyPrintSection(response, value, builder); + + response.getOutputStream().write(("
" + applicationVersion).getBytes(UTF_8)); + } + + + void addGreetingSection(StringBuilder builder, HttpServletRequest request) { + + String username = userAuthenticationService.getCurrentUser().getName(); + builder.append("

Hello ").append(username).append(" from ").append(request.getRemoteHost()).append("

"); + builder.append("This is a RESTful webservice.
"); + } + + + void addJSONPrettyPrintSection(HttpServletResponse response, Map value, StringBuilder builder) + throws IOException { + + builder.append("

JSON

");
+
+        response.getOutputStream().write(builder.toString().getBytes(UTF_8));
+
+        ObjectWriter writer = objectMapper.writerWithDefaultPrettyPrinter();
+        response.getOutputStream().write(writer.writeValueAsBytes(value));
+        response.getOutputStream().write("
".getBytes(UTF_8)); + } + + + void addFormatsSection(HttpServletRequest request, StringBuilder builder) { + + String accept = request.getHeader("Accept"); + StringBuffer urlB = request.getRequestURL(); + String q = request.getQueryString(); + + if (q != null) { + urlB.append('?').append(q); + } + + builder.append("

Request and Formats

"); + + String url = urlB.toString(); + + builder.append("You requested this page using url ").append(url).append(" and Accept-Header ").append( + accept).append("

"); + + builder.append("You requested this page as HTML (Probably because your client has text/html " + + "prior to application/json or application/xml in the Accept-Header."); + builder.append("If you are intersted in another format please adjust your Accept-Header " + + "or append .json or .xml at the url:
    "); + + for (String type : Arrays.asList("json")) { + String typeUrl = url + "." + type; + + if (q != null) { + typeUrl = request.getRequestURL().append(".").append(type).append("?").append(q).toString(); + } + + builder.append("
  • Show as ").append(type).append(" (").append( + typeUrl).append(")
  • "); + } + + builder.append("
"); + } + + + void addLinkSection(StringBuilder builder, Map value) { + + List links = new ArrayList<>(); + + for (Map.Entry entry : value.entrySet()) { + Object v = entry.getValue(); + + if (v instanceof ResourceSupport) { + links = ((ResourceSupport) v).getLinks(); + } + } + + builder.append("

Links

"); + + builder.append("You can navigate to the following other resources from here:
    "); + + String link; + String decodedLink; + + for (Link singleLink : links) { + link = singleLink.getHref(); + + try { + decodedLink = URLDecoder.decode(link, UTF_8); + } catch (UnsupportedEncodingException e) { + decodedLink = link; + } + + if (decodedLink.contains("{")) { + builder.append("
  • ").append(singleLink.getRel()).append(": ").append(decodedLink).append( + " (url contains placeholders)
  • "); + } else { + builder.append("
  • ").append(singleLink.getRel()).append( + " (").append(decodedLink).append(")
  • "); + } + } + + builder.append("
"); + } +} diff --git a/src/main/java/net/contargo/iris/co2/advice/Co2BargeRegionMap.java b/src/main/java/net/contargo/iris/co2/advice/Co2BargeRegionMap.java new file mode 100644 index 00000000..7c38cf9c --- /dev/null +++ b/src/main/java/net/contargo/iris/co2/advice/Co2BargeRegionMap.java @@ -0,0 +1,92 @@ +package net.contargo.iris.co2.advice; + +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.terminal.Region; + +import java.math.BigDecimal; + +import java.util.HashMap; +import java.util.Map; + +import static net.contargo.iris.container.ContainerState.FULL; +import static net.contargo.iris.route.RoutePart.Direction.DOWNSTREAM; + +import static java.util.Collections.unmodifiableMap; + + +/** + * @author Oliver Messner - messner@synyx.de + */ +class Co2BargeRegionMap { + + static final String FULL_UPSTREAM = "FullUpstream"; + static final String FULL_DOWNSTREAM = "FullDownstream"; + static final String EMPTY_UPSTREAM = "EmptyUpstream"; + static final String EMPTY_DOWNSTREAM = "EmptyDownstream"; + + static final Map NIEDERRHEIN_CO2_MAP; + + static { + Map map = new HashMap<>(); + map.put(FULL_UPSTREAM, new BigDecimal("0.31")); + map.put(EMPTY_UPSTREAM, new BigDecimal("0.27")); + map.put(FULL_DOWNSTREAM, new BigDecimal("0.17")); + map.put(EMPTY_DOWNSTREAM, new BigDecimal("0.14")); + NIEDERRHEIN_CO2_MAP = unmodifiableMap(map); + } + + static final Map OBERRHEIN_CO2_MAP; + + static { + Map map = new HashMap<>(); + map.put(FULL_UPSTREAM, new BigDecimal("0.43")); + map.put(EMPTY_UPSTREAM, new BigDecimal("0.4")); + map.put(FULL_DOWNSTREAM, new BigDecimal("0.23")); + map.put(EMPTY_DOWNSTREAM, new BigDecimal("0.21")); + OBERRHEIN_CO2_MAP = unmodifiableMap(map); + } + + static final Map SCHELDE_CO2_MAP; + + static { + Map map = new HashMap<>(); + map.put(FULL_UPSTREAM, new BigDecimal("0.427")); + map.put(FULL_DOWNSTREAM, new BigDecimal("0.427")); + map.put(EMPTY_UPSTREAM, new BigDecimal("0.375")); + map.put(EMPTY_DOWNSTREAM, new BigDecimal("0.375")); + SCHELDE_CO2_MAP = unmodifiableMap(map); + } + + BigDecimal getCo2Factor(Region region, RoutePart routePart) { + + return getCo2Map(region).get(getCo2FactorsKey(routePart)); + } + + + String getCo2FactorsKey(RoutePart routePart) { + + if (routePart.getContainerState() == FULL) { + return routePart.getDirection() == DOWNSTREAM ? FULL_DOWNSTREAM : FULL_UPSTREAM; + } else { + return routePart.getDirection() == DOWNSTREAM ? EMPTY_DOWNSTREAM : EMPTY_UPSTREAM; + } + } + + + private Map getCo2Map(Region region) { + + if (region == Region.NIEDERRHEIN || region == Region.NOT_SET) { + return NIEDERRHEIN_CO2_MAP; + } + + if (region == Region.OBERRHEIN) { + return OBERRHEIN_CO2_MAP; + } + + if (region == Region.SCHELDE) { + return SCHELDE_CO2_MAP; + } + + throw new IllegalStateException("There is no Co2 Map for region: " + region); + } +} diff --git a/src/main/java/net/contargo/iris/co2/advice/Co2PartBargeStrategy.java b/src/main/java/net/contargo/iris/co2/advice/Co2PartBargeStrategy.java new file mode 100644 index 00000000..4090b164 --- /dev/null +++ b/src/main/java/net/contargo/iris/co2/advice/Co2PartBargeStrategy.java @@ -0,0 +1,37 @@ +package net.contargo.iris.co2.advice; + +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RoutePartData; +import net.contargo.iris.terminal.Terminal; + +import java.math.BigDecimal; + + +/** + * Co2 strategy for the main run connection with route type barge. + * + * @author Oliver Messner - messner@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +class Co2PartBargeStrategy implements Co2PartStrategy { + + private final Co2BargeRegionMap co2BargeRegionMap; + + public Co2PartBargeStrategy(Co2BargeRegionMap co2BargeRegionMap) { + + this.co2BargeRegionMap = co2BargeRegionMap; + } + + @Override + public BigDecimal getEmissionForRoutePart(RoutePart routePart) { + + RoutePartData routePartData = routePart.getData(); + Terminal terminal = routePart.findTerminal(); + + BigDecimal distance1 = routePartData.getDieselDistance(); + + BigDecimal co2Factor = co2BargeRegionMap.getCo2Factor(terminal.getRegion(), routePart); + + return distance1.multiply(co2Factor); + } +} diff --git a/src/main/java/net/contargo/iris/co2/advice/Co2PartRailStrategy.java b/src/main/java/net/contargo/iris/co2/advice/Co2PartRailStrategy.java new file mode 100644 index 00000000..d5dbe570 --- /dev/null +++ b/src/main/java/net/contargo/iris/co2/advice/Co2PartRailStrategy.java @@ -0,0 +1,46 @@ +package net.contargo.iris.co2.advice; + +import net.contargo.iris.container.ContainerState; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RoutePartData; + +import java.math.BigDecimal; + + +/** + * @author Oliver Messner - messner@synyx.de + */ +class Co2PartRailStrategy implements Co2PartStrategy { + + private static final BigDecimal CO2_RAIL_FULL_DIESEL = BigDecimal.valueOf(0.5); + private static final BigDecimal CO2_RAIL_EMPTY_DIESEL = BigDecimal.valueOf(0.4); + private static final BigDecimal CO2_RAIL_FULL_ELEKTRO = BigDecimal.valueOf(0.34); + private static final BigDecimal CO2_RAIL_EMPTY_ELEKTRO = BigDecimal.valueOf(0.27); + + @Override + public BigDecimal getEmissionForRoutePart(RoutePart routePart) { + + BigDecimal co2 = BigDecimal.ZERO; + + RoutePartData routePartData = routePart.getData(); + + BigDecimal distance1 = routePartData.getDieselDistance(); + BigDecimal distance2 = routePartData.getElectricDistance(); + + BigDecimal co2DieselFactor; + BigDecimal co2ElektroFactor; + + if (ContainerState.FULL == routePart.getContainerState()) { + co2DieselFactor = CO2_RAIL_FULL_DIESEL; + co2ElektroFactor = CO2_RAIL_FULL_ELEKTRO; + } else { + co2DieselFactor = CO2_RAIL_EMPTY_DIESEL; + co2ElektroFactor = CO2_RAIL_EMPTY_ELEKTRO; + } + + co2 = co2.add(distance1.multiply(co2DieselFactor)); + co2 = co2.add(distance2.multiply(co2ElektroFactor)); + + return co2; + } +} diff --git a/src/main/java/net/contargo/iris/co2/advice/Co2PartStrategy.java b/src/main/java/net/contargo/iris/co2/advice/Co2PartStrategy.java new file mode 100644 index 00000000..7fa7a2ce --- /dev/null +++ b/src/main/java/net/contargo/iris/co2/advice/Co2PartStrategy.java @@ -0,0 +1,21 @@ +package net.contargo.iris.co2.advice; + +import net.contargo.iris.route.RoutePart; + +import java.math.BigDecimal; + + +/** + * @author Oliver Messner - messner@synyx.de + */ +public interface Co2PartStrategy { + + /** + * Calculates the co2 emission of a specific {@link RoutePart}. + * + * @param routePart to calculate co2 emission for + * + * @return co2 emission + */ + BigDecimal getEmissionForRoutePart(RoutePart routePart); +} diff --git a/src/main/java/net/contargo/iris/co2/advice/Co2PartStrategyAdvisor.java b/src/main/java/net/contargo/iris/co2/advice/Co2PartStrategyAdvisor.java new file mode 100644 index 00000000..390f3430 --- /dev/null +++ b/src/main/java/net/contargo/iris/co2/advice/Co2PartStrategyAdvisor.java @@ -0,0 +1,63 @@ +package net.contargo.iris.co2.advice; + +import net.contargo.iris.route.RouteType; + + +/** + * @author Oliver Messner - messner@synyx.de + */ +public class Co2PartStrategyAdvisor { + + private Co2PartStrategy bargeStrategy; + private Co2PartStrategy railStrategy; + private Co2PartStrategy truckStrategy; + + Co2PartStrategyAdvisor() { + + // no-arg constructor used for unit testing + } + + + public Co2PartStrategyAdvisor(Co2PartStrategy bargeStrategy, Co2PartStrategy railStrategy, + Co2PartStrategy truckStrategy) { + + this.bargeStrategy = bargeStrategy; + this.railStrategy = railStrategy; + this.truckStrategy = truckStrategy; + } + + void setBargeStrategy(Co2PartStrategy bargeStrategy) { + + this.bargeStrategy = bargeStrategy; + } + + + void setRailStrategy(Co2PartStrategy railStrategy) { + + this.railStrategy = railStrategy; + } + + + void setTruckStrategy(Co2PartStrategy truckStrategy) { + + this.truckStrategy = truckStrategy; + } + + + public Co2PartStrategy advice(RouteType routeType) { + + if (routeType == RouteType.BARGE) { + return bargeStrategy; + } + + if (routeType == RouteType.RAIL) { + return railStrategy; + } + + if (routeType == RouteType.TRUCK) { + return truckStrategy; + } + + throw new IllegalStateException("Cannot determine co2 for route part of type " + routeType); + } +} diff --git a/src/main/java/net/contargo/iris/co2/advice/Co2PartTruckStrategy.java b/src/main/java/net/contargo/iris/co2/advice/Co2PartTruckStrategy.java new file mode 100644 index 00000000..de05d685 --- /dev/null +++ b/src/main/java/net/contargo/iris/co2/advice/Co2PartTruckStrategy.java @@ -0,0 +1,35 @@ +package net.contargo.iris.co2.advice; + +import net.contargo.iris.container.ContainerState; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RoutePartData; + +import java.math.BigDecimal; + + +/** + * @author Oliver Messner - messner@synyx.de + */ +class Co2PartTruckStrategy implements Co2PartStrategy { + + private static final BigDecimal CO2_TRUCK_FULL = BigDecimal.valueOf(0.88); + private static final BigDecimal CO2_TRUCK_EMPTY = BigDecimal.valueOf(0.73); + + @Override + public BigDecimal getEmissionForRoutePart(RoutePart routePart) { + + RoutePartData routePartData = routePart.getData(); + + BigDecimal co2Factor; + + if (ContainerState.FULL == routePart.getContainerState()) { + co2Factor = CO2_TRUCK_FULL; + } else { + co2Factor = CO2_TRUCK_EMPTY; + } + + BigDecimal distance = routePartData.getDistance(); + + return distance.multiply(co2Factor); + } +} diff --git a/src/main/java/net/contargo/iris/co2/service/Co2Service.java b/src/main/java/net/contargo/iris/co2/service/Co2Service.java new file mode 100644 index 00000000..5f8011eb --- /dev/null +++ b/src/main/java/net/contargo/iris/co2/service/Co2Service.java @@ -0,0 +1,32 @@ +package net.contargo.iris.co2.service; + +import net.contargo.iris.route.Route; + +import java.math.BigDecimal; + + +/** + * @author Aljona Murygina - murygina@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public interface Co2Service { + + /** + * Get Co2 emission for the given route. + * + * @param route to extract co2 emission + * + * @return Co2 in kg + */ + BigDecimal getEmission(Route route); + + + /** + * Get Co2 emission for the given route if it would be a direct truck route. + * + * @param route to extract truck co2 emission + * + * @return Co2 in kg + */ + BigDecimal getEmissionDirectTruck(Route route); +} diff --git a/src/main/java/net/contargo/iris/co2/service/Co2ServiceImpl.java b/src/main/java/net/contargo/iris/co2/service/Co2ServiceImpl.java new file mode 100644 index 00000000..008b4432 --- /dev/null +++ b/src/main/java/net/contargo/iris/co2/service/Co2ServiceImpl.java @@ -0,0 +1,114 @@ +package net.contargo.iris.co2.service; + +import net.contargo.iris.co2.advice.Co2PartStrategy; +import net.contargo.iris.co2.advice.Co2PartStrategyAdvisor; +import net.contargo.iris.route.DirectTruckRouteBuilder; +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RouteType; + +import org.slf4j.Logger; + +import java.lang.invoke.MethodHandles; + +import java.math.BigDecimal; + +import java.util.List; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * @author Aljona Murygina - murygina@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +class Co2ServiceImpl implements Co2Service { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + // Umschlag "CO2 Handling" + private static final BigDecimal CO2_HANDLING = BigDecimal.valueOf(8); + + // Umschlag "Direkttruck Oneway" + private static final BigDecimal CO2_TRUCK_ONE_WAY = BigDecimal.valueOf(4); + + private final DirectTruckRouteBuilder directTruckRouteBuilder; + private final Co2PartStrategyAdvisor co2PartStrategyAdvisor; + + Co2ServiceImpl(DirectTruckRouteBuilder directTruckRouteBuilder, Co2PartStrategyAdvisor co2PartStrategyAdvisor) { + + this.directTruckRouteBuilder = directTruckRouteBuilder; + this.co2PartStrategyAdvisor = co2PartStrategyAdvisor; + } + + @Override + public BigDecimal getEmission(Route route) { + + BigDecimal co2 = BigDecimal.ZERO; + + List parts = route.getData().getParts(); + + for (int i = 0; i < parts.size(); i++) { + RoutePart part = parts.get(i); + RouteType type = part.getRouteType(); + + Co2PartStrategy strategy = co2PartStrategyAdvisor.advice(type); + co2 = co2.add(strategy.getEmissionForRoutePart(part)); + + if (co2HandlingRequired(parts, i)) { + co2 = co2.add(CO2_HANDLING); + } + } + + if (!route.isRoundTrip()) { + co2 = co2.add(CO2_TRUCK_ONE_WAY); + } + + LOG.debug("Setting CO2 for route {}: {} kg", route.getName(), co2); + + return co2; + } + + + @Override + public BigDecimal getEmissionDirectTruck(Route route) { + + Route truckRoute = directTruckRouteBuilder.getCorrespondingDirectTruckRoute(route); + + BigDecimal co2 = BigDecimal.ZERO; + List parts = truckRoute.getData().getParts(); + + for (RoutePart part : parts) { + Co2PartStrategy strategy = co2PartStrategyAdvisor.advice(RouteType.TRUCK); + co2 = co2.add(strategy.getEmissionForRoutePart(part)); + } + + if (!truckRoute.isRoundTrip()) { + co2 = co2.add(CO2_TRUCK_ONE_WAY); + } + + LOG.debug("Setting CO2 Direct Truck for route {}: {} kg", route.getName(), co2); + + return co2; + } + + + /** + * If there a is a change in transport, you have to consider Co2-Handling in emission calculation. (e.g. from truck + * route to rail route). Please notice that there are two different Co2-Handling values: one for direct truck and + * one for transport with main run (barge or rail) + * + * @param routeParts + * @param indexOfRoutePart + * + * @return true if Co2 Handling is to be considered in emission calculation, false if not + */ + private boolean co2HandlingRequired(List routeParts, int indexOfRoutePart) { + + RoutePart part = routeParts.get(indexOfRoutePart); + + // if this part is not the last route part AND the next part is not of the same route type, then return true + return (indexOfRoutePart < (routeParts.size() - 1)) + && (!part.isOfType(routeParts.get(indexOfRoutePart + 1).getRouteType())); + } +} diff --git a/src/main/java/net/contargo/iris/config/SwaggerConfig.java b/src/main/java/net/contargo/iris/config/SwaggerConfig.java new file mode 100644 index 00000000..212b0ab8 --- /dev/null +++ b/src/main/java/net/contargo/iris/config/SwaggerConfig.java @@ -0,0 +1,46 @@ +package net.contargo.iris.config; + +import com.mangofactory.swagger.configuration.SpringSwaggerConfig; +import com.mangofactory.swagger.models.dto.ApiInfo; +import com.mangofactory.swagger.plugin.EnableSwagger; +import com.mangofactory.swagger.plugin.SwaggerSpringMvcPlugin; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import org.springframework.ui.Model; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +@Configuration +@EnableSwagger +public class SwaggerConfig { + + private SpringSwaggerConfig springSwaggerConfig; + + @Autowired + public void setSpringSwaggerConfig(SpringSwaggerConfig springSwaggerConfig) { + + this.springSwaggerConfig = springSwaggerConfig; + } + + + @Bean + public SwaggerSpringMvcPlugin customImplementation() { + + this.springSwaggerConfig.defaultSwaggerPathProvider().setApiResourcePrefix("api"); + + return new SwaggerSpringMvcPlugin(this.springSwaggerConfig).apiInfo(apiInfo()).ignoredParameterTypes( + Model.class); + } + + + private ApiInfo apiInfo() { + + return new ApiInfo("IRIS Backend Api", null, null, null, null, null); + } +} diff --git a/src/main/java/net/contargo/iris/connection/MainRunConnection.java b/src/main/java/net/contargo/iris/connection/MainRunConnection.java new file mode 100644 index 00000000..1dbd61d5 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/MainRunConnection.java @@ -0,0 +1,212 @@ +package net.contargo.iris.connection; + +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + +import net.contargo.validation.bigdecimal.BigDecimalValidate; + +import java.math.BigDecimal; + +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import javax.validation.constraints.NotNull; + + +/** + * Represents a connection between a {@link Terminal} and a {@link Seaport}. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Vincent Potucek - potucek@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +@Entity +@Table( + name = "Connection", + uniqueConstraints = @UniqueConstraint(columnNames = { "seaport_id", "terminal_id", "routeType" }) +) +public class MainRunConnection { + + private static final long TEN = 10L; + + @Id + @GeneratedValue + private Long id; + + @NotNull + @ManyToOne + private Seaport seaport; + + @NotNull + @ManyToOne + private Terminal terminal; + + @NotNull + @BigDecimalValidate(minValue = 0, minDecimalPlaces = 1L, maxDecimalPlaces = TEN, maxFractionalPlaces = TEN) + private BigDecimal dieselDistance; + + @NotNull + @BigDecimalValidate(minValue = 0, minDecimalPlaces = 1L, maxDecimalPlaces = TEN, maxFractionalPlaces = TEN) + private BigDecimal electricDistance; + + @NotNull + @Enumerated(EnumType.STRING) + private RouteType routeType; + + @NotNull + private Boolean enabled = Boolean.TRUE; + + public MainRunConnection() { + + // JPA needs no-arg constructor + } + + + public MainRunConnection(Seaport seaport) { + + super(); + this.seaport = seaport; + } + + /** + * Computes the total distance of this {@link MainRunConnection} as the sum of its diesel distance and its + * electrical distance. + * + * @return total distance + */ + public BigDecimal getTotalDistance() { + + return getDieselDistance().add(getElectricDistance()); + } + + + public Long getId() { + + return id; + } + + + public void setId(Long id) { + + this.id = id; + } + + + public Seaport getSeaport() { + + return seaport; + } + + + public void setSeaport(Seaport seaport) { + + this.seaport = seaport; + } + + + public Terminal getTerminal() { + + return terminal; + } + + + public void setTerminal(Terminal terminal) { + + this.terminal = terminal; + } + + + public BigDecimal getDieselDistance() { + + return dieselDistance; + } + + + public void setDieselDistance(BigDecimal dieselDistance) { + + this.dieselDistance = dieselDistance; + } + + + public BigDecimal getElectricDistance() { + + return electricDistance; + } + + + public void setElectricDistance(BigDecimal electricDistance) { + + this.electricDistance = electricDistance; + } + + + public RouteType getRouteType() { + + return routeType; + } + + + public void setRouteType(RouteType routeType) { + + this.routeType = routeType; + } + + + public Boolean getEnabled() { + + return enabled; + } + + + public void setEnabled(Boolean enabled) { + + this.enabled = enabled; + } + + + /** + * Checks whether this {@link MainRunConnection} along with both its {@link Seaport} and {@link Terminal}. + * + * @return true if each of them is enabled, false otherwise + */ + public Boolean getEverythingEnabled() { + + if (!enabled) { + return false; + } + + if (seaport == null) { + return false; + } + + if (!seaport.isEnabled()) { + return false; + } + + if (null == terminal) { + return false; + } + + if (!terminal.isEnabled()) { + return false; + } + + return true; + } + + + @Override + public String toString() { + + return "MainRunConnection [id=" + id + ", seaport=" + seaport + + ", location=" + terminal + ", dieselDistance=" + dieselDistance + ", electricDistance=" + electricDistance + + ", routeType=" + routeType + "]"; + } +} diff --git a/src/main/java/net/contargo/iris/connection/advice/MainRunAdvisor.java b/src/main/java/net/contargo/iris/connection/advice/MainRunAdvisor.java new file mode 100644 index 00000000..ffc317ad --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/advice/MainRunAdvisor.java @@ -0,0 +1,80 @@ +package net.contargo.iris.connection.advice; + +import net.contargo.iris.route.RouteDirection; +import net.contargo.iris.route.RouteProduct; + +import static net.contargo.iris.route.RouteDirection.IMPORT; +import static net.contargo.iris.route.RouteProduct.ROUNDTRIP; + + +/** + * Determines {@link MainRunStrategy} strategy for building routes based on {@link RouteProduct} and + * {@link RouteDirection}. + * + * @author Jörg Alberto Hoffmann - hoffmann@synyx.de + */ +public class MainRunAdvisor { + + private MainRunStrategy mainRunRoundTripImportAdvice; + private MainRunStrategy mainRunRoundTripExportAdvice; + private MainRunStrategy mainRunOneWayImportAdvice; + private MainRunStrategy mainRunOneWayExportAdvice; + + MainRunAdvisor(MainRunStrategy mainRunRoundTripImportAdvice, MainRunStrategy mainRunRoundTripExportAdvice, + MainRunStrategy mainRunOneWayImportAdvice, MainRunStrategy mainRunOneWayExportAdvice) { + + this.mainRunRoundTripImportAdvice = mainRunRoundTripImportAdvice; + this.mainRunRoundTripExportAdvice = mainRunRoundTripExportAdvice; + this.mainRunOneWayImportAdvice = mainRunOneWayImportAdvice; + this.mainRunOneWayExportAdvice = mainRunOneWayExportAdvice; + } + + void setMainRunRoundTripImportAdvice(MainRunStrategy mainRunRoundTripImportAdvice) { + + this.mainRunRoundTripImportAdvice = mainRunRoundTripImportAdvice; + } + + + void setMainRunRoundTripExportAdvice(MainRunStrategy mainRunRoundTripExportAdvice) { + + this.mainRunRoundTripExportAdvice = mainRunRoundTripExportAdvice; + } + + + void setMainRunOneWayImportAdvice(MainRunStrategy mainRunOneWayImportAdvice) { + + this.mainRunOneWayImportAdvice = mainRunOneWayImportAdvice; + } + + + void setMainRunOneWayExportAdvice(MainRunStrategy mainRunOneWayExportAdvice) { + + this.mainRunOneWayExportAdvice = mainRunOneWayExportAdvice; + } + + + /** + * Determines concrete MainRunStrategy strategy based on given routeProduct and routeDirection. + * + * @param routeProduct + * @param routeDirection + * + * @return + */ + public MainRunStrategy advice(RouteProduct routeProduct, RouteDirection routeDirection) { + + if (ROUNDTRIP.equals(routeProduct)) { + if (IMPORT.equals(routeDirection)) { + return mainRunRoundTripImportAdvice; + } else { + return mainRunRoundTripExportAdvice; + } + } else { + if (IMPORT.equals(routeDirection)) { + return mainRunOneWayImportAdvice; + } else { + return mainRunOneWayExportAdvice; + } + } + } +} diff --git a/src/main/java/net/contargo/iris/connection/advice/MainRunOneWayExportStrategy.java b/src/main/java/net/contargo/iris/connection/advice/MainRunOneWayExportStrategy.java new file mode 100644 index 00000000..3ab1cfc8 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/advice/MainRunOneWayExportStrategy.java @@ -0,0 +1,36 @@ +package net.contargo.iris.connection.advice; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.container.ContainerState; +import net.contargo.iris.container.ContainerType; +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteBuilder; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + + +/** + * @author Jörg Alberto Hoffmann - hoffmann@synyx.de + * @see MainRunStrategy + * @see MainRunAdvisor + */ +class MainRunOneWayExportStrategy implements MainRunStrategy { + + /** + * @see MainRunStrategy#getRoute(Seaport, GeoLocation, Terminal, ContainerType, RouteType) + */ + @Override + public Route getRoute(Seaport seaPort, GeoLocation destination, Terminal terminal, ContainerType containerType, + RouteType mainRunRouteType) { + + RouteBuilder routeBuilder = new RouteBuilder(terminal, containerType, ContainerState.EMPTY); + routeBuilder.goTo(destination, RouteType.TRUCK); + routeBuilder.loadContainer(); + routeBuilder.goTo(terminal, RouteType.TRUCK); + routeBuilder.goTo(seaPort, mainRunRouteType); + routeBuilder.responsibleTerminal(terminal); + + return routeBuilder.getRoute(); + } +} diff --git a/src/main/java/net/contargo/iris/connection/advice/MainRunOneWayImportStrategy.java b/src/main/java/net/contargo/iris/connection/advice/MainRunOneWayImportStrategy.java new file mode 100644 index 00000000..c8b2ed41 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/advice/MainRunOneWayImportStrategy.java @@ -0,0 +1,36 @@ +package net.contargo.iris.connection.advice; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.container.ContainerState; +import net.contargo.iris.container.ContainerType; +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteBuilder; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + + +/** + * @author Jörg Alberto Hoffmann - hoffmann@synyx.de + * @see MainRunStrategy + * @see MainRunAdvisor + */ +class MainRunOneWayImportStrategy implements MainRunStrategy { + + /** + * @see MainRunStrategy#getRoute(Seaport, GeoLocation, Terminal, ContainerType, RouteType) + */ + @Override + public Route getRoute(Seaport seaPort, GeoLocation destination, Terminal terminal, ContainerType containerType, + RouteType mainRunRouteType) { + + RouteBuilder routeBuilder = new RouteBuilder(seaPort, containerType, ContainerState.FULL); + routeBuilder.goTo(terminal, mainRunRouteType); + routeBuilder.goTo(destination, RouteType.TRUCK); + routeBuilder.unloadContainer(); + routeBuilder.goTo(terminal, RouteType.TRUCK); + routeBuilder.responsibleTerminal(terminal); + + return routeBuilder.getRoute(); + } +} diff --git a/src/main/java/net/contargo/iris/connection/advice/MainRunRoundTripExportStrategy.java b/src/main/java/net/contargo/iris/connection/advice/MainRunRoundTripExportStrategy.java new file mode 100644 index 00000000..4f997524 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/advice/MainRunRoundTripExportStrategy.java @@ -0,0 +1,37 @@ +package net.contargo.iris.connection.advice; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.container.ContainerState; +import net.contargo.iris.container.ContainerType; +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteBuilder; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + + +/** + * @author Jörg Alberto Hoffmann - hoffmann@synyx.de + * @see MainRunStrategy + * @see MainRunAdvisor + */ +class MainRunRoundTripExportStrategy implements MainRunStrategy { + + /** + * @see MainRunStrategy#getRoute(Seaport, GeoLocation, Terminal, ContainerType, RouteType) + */ + @Override + public Route getRoute(Seaport seaPort, GeoLocation destination, Terminal terminal, ContainerType containerType, + RouteType mainRunRouteType) { + + RouteBuilder routeBuilder = new RouteBuilder(seaPort, containerType, ContainerState.EMPTY); + routeBuilder.goTo(terminal, mainRunRouteType); + routeBuilder.goTo(destination, RouteType.TRUCK); + routeBuilder.loadContainer(); + routeBuilder.goTo(terminal, RouteType.TRUCK); + routeBuilder.goTo(seaPort, mainRunRouteType); + routeBuilder.responsibleTerminal(terminal); + + return routeBuilder.getRoute(); + } +} diff --git a/src/main/java/net/contargo/iris/connection/advice/MainRunRoundTripImportStrategy.java b/src/main/java/net/contargo/iris/connection/advice/MainRunRoundTripImportStrategy.java new file mode 100644 index 00000000..29aa6c55 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/advice/MainRunRoundTripImportStrategy.java @@ -0,0 +1,37 @@ +package net.contargo.iris.connection.advice; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.container.ContainerState; +import net.contargo.iris.container.ContainerType; +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteBuilder; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + + +/** + * @author Jörg Alberto Hoffmann - hoffmann@synyx.de + * @see MainRunStrategy + * @see MainRunAdvisor + */ +class MainRunRoundTripImportStrategy implements MainRunStrategy { + + /** + * @see MainRunStrategy#getRoute(Seaport, GeoLocation, Terminal, ContainerType, RouteType) + */ + @Override + public Route getRoute(Seaport seaPort, GeoLocation destination, Terminal terminal, ContainerType containerType, + RouteType mainRunRouteType) { + + RouteBuilder routeBuilder = new RouteBuilder(seaPort, containerType, ContainerState.FULL); + routeBuilder.goTo(terminal, mainRunRouteType); + routeBuilder.goTo(destination, RouteType.TRUCK); + routeBuilder.unloadContainer(); + routeBuilder.goTo(terminal, RouteType.TRUCK); + routeBuilder.goTo(seaPort, mainRunRouteType); + routeBuilder.responsibleTerminal(terminal); + + return routeBuilder.getRoute(); + } +} diff --git a/src/main/java/net/contargo/iris/connection/advice/MainRunStrategy.java b/src/main/java/net/contargo/iris/connection/advice/MainRunStrategy.java new file mode 100644 index 00000000..bbcec7db --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/advice/MainRunStrategy.java @@ -0,0 +1,32 @@ +package net.contargo.iris.connection.advice; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.container.ContainerType; +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + + +/** + * Strategy component for building a concrete route. + * + * @author Jörg Alberto Hoffmann - hoffmann@synyx.de + * @see MainRunAdvisor + */ +public interface MainRunStrategy { + + /** + * Builds a {@link Route} based on given parameters. + * + * @param seaport the {@link Route}'s {@link Seaport} + * @param destination the {@link Route}'s destination + * @param terminal the {@link Route}'s {@link Terminal} + * @param containerType the {@link Route}'s {@link ContainerType} + * @param routeType the {@link Route}'s {@link RouteType} + * + * @return + */ + Route getRoute(Seaport seaport, GeoLocation destination, Terminal terminal, ContainerType containerType, + RouteType routeType); +} diff --git a/src/main/java/net/contargo/iris/connection/api/MainRunConnectionApiController.java b/src/main/java/net/contargo/iris/connection/api/MainRunConnectionApiController.java new file mode 100644 index 00000000..6e1c5599 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/api/MainRunConnectionApiController.java @@ -0,0 +1,165 @@ +package net.contargo.iris.connection.api; + +import com.wordnik.swagger.annotations.ApiOperation; + +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.connection.dto.MainRunConnectionDto; +import net.contargo.iris.connection.dto.MainRunConnectionDtoService; +import net.contargo.iris.connection.dto.RouteDto; +import net.contargo.iris.connection.dto.SeaportConnectionRoutesDtoService; +import net.contargo.iris.connection.dto.SeaportTerminalConnectionDtoService; +import net.contargo.iris.container.ContainerType; +import net.contargo.iris.route.RouteCombo; +import net.contargo.iris.route.RouteInformation; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.dto.SeaportDto; +import net.contargo.iris.seaport.dto.SeaportDtoService; + +import org.slf4j.Logger; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.lang.invoke.MethodHandles; + +import java.math.BigInteger; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.slf4j.LoggerFactory.getLogger; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + + +/** + * Public API controller that responds to request for {@link net.contargo.iris.connection.MainRunConnection}s. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Oliver Messner - messner@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@Controller +@RequestMapping(value = "/connections") +public class MainRunConnectionApiController extends AbstractController { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private static final String ROUTE_DETAILS_URL = "/routedetails"; + private static final String ROUTE_PART_DETAILS_URL = "/routepartdetails"; + + private final SeaportDtoService seaportDtoService; + private final SeaportConnectionRoutesDtoService seaportConnectionRoutesDtoService; + private final RouteUrlSerializationService routeUrlSerializationService; + private final MainRunConnectionDtoService connectionApiDtoService; + private final SeaportTerminalConnectionDtoService seaportTerminalConnectionDtoService; + + @Autowired + public MainRunConnectionApiController(SeaportDtoService seaportDtoService, + SeaportConnectionRoutesDtoService seaportConnectionRoutesDtoService, + RouteUrlSerializationService routeUrlSerializationService, MainRunConnectionDtoService connectionApiDtoService, + SeaportTerminalConnectionDtoService seaportTerminalConnectionDtoService) { + + this.seaportDtoService = seaportDtoService; + this.seaportConnectionRoutesDtoService = seaportConnectionRoutesDtoService; + this.routeUrlSerializationService = routeUrlSerializationService; + this.connectionApiDtoService = connectionApiDtoService; + this.seaportTerminalConnectionDtoService = seaportTerminalConnectionDtoService; + } + + @ApiOperation( + value = "Returns a list of all possible connection routes between a seaport and a destination address.", + notes = "Returns a list of all possible connection routes between a seaport and a destination address." + ) + @RequestMapping(value = SLASH + "{seaportuid}" + SLASH + "{lat}:{lon}" + SLASH + "{isroundtrip}", method = GET) + @ModelAttribute(RESPONSE) + public RoutesResponse getSeaportRoutes(@PathVariable("seaportuid") BigInteger seaportUid, + @PathVariable("lat") double latitude, + @PathVariable("lon") double longitude, + @PathVariable("isroundtrip") boolean isRoundTrip, + @RequestParam(value = "containerType", required = false) ContainerType containerType, + @RequestParam(value = "isImport", defaultValue = "true") boolean isImport, + @RequestParam(value = "combo", defaultValue = "ALL") RouteCombo routeCombo) { + + RouteInformation routeInformation = new RouteInformation(containerType, routeCombo, latitude, longitude, + isRoundTrip, isImport); + + SeaportDto seaport = seaportDtoService.getByUid(seaportUid); + + List routes = seaportConnectionRoutesDtoService.getAvailableSeaportConnectionRoutes(seaport, + routeInformation); + + for (RouteDto route : routes) { + routeUrlSerializationService.serializeUrl(route, ROUTE_DETAILS_URL, ROUTE_PART_DETAILS_URL); + } + + RoutesResponse response = new RoutesResponse(); + + response.add(linkTo( + methodOn(getClass()).getSeaportRoutes(seaportUid, latitude, longitude, isRoundTrip, containerType, + isImport, routeCombo)).withSelfRel()); + + response.setRoutes(routes); + + LOG.info("API: Responding with {} connections for seaportsconnections-request: seaport {} to {}:{}" + + " with isRoundtrip {} containerType {} and isimport {} ", routes.size(), seaportUid, latitude, longitude, + isRoundTrip, containerType, isImport); + + return response; + } + + + @ApiOperation( + value = "Returns all seaports that have a connection of a given route-combo.", + notes = "Returns all seaports that have a connection of a given route-combo." + ) + @RequestMapping(value = SLASH + "seaports", method = GET) + @ResponseBody + public SeaportsResponse getSeaportsInConnections( + @RequestParam(value = "combo", defaultValue = "ALL") RouteCombo routeCombo) { + + SeaportsResponse response = new SeaportsResponse(); + + response.add(linkTo(methodOn(getClass()).getSeaportsInConnections(routeCombo)).withSelfRel()); + + Set ports = new HashSet<>(); + + for (RouteType t : routeCombo.getRouteTypes()) { + ports.addAll(seaportTerminalConnectionDtoService.findSeaportsConnectedByRouteType(t)); + } + + response.setSeaports(ports); + + LOG.info("API: Returning {} active seaports in connections with combo {}", ports.size(), routeCombo); + + return response; + } + + + @ApiOperation( + value = "Returns all Connections containing the given terminal", + notes = "Returns all Connections containing the given terminal", response = MainRunConnectionDto.class, + responseContainer = "List" + ) + @RequestMapping(method = GET, params = "terminalUid", produces = "application/json") + @ResponseBody + public Collection getConnectionsForTerminal( + @RequestParam("terminalUid") BigInteger terminalUid) { + + LOG.info("API: client requests connections for terminal UID: " + terminalUid); + + return connectionApiDtoService.getConnectionsForTerminal(terminalUid); + } +} diff --git a/src/main/java/net/contargo/iris/connection/api/RouteUrlSerializationService.java b/src/main/java/net/contargo/iris/connection/api/RouteUrlSerializationService.java new file mode 100644 index 00000000..f9a4162f --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/api/RouteUrlSerializationService.java @@ -0,0 +1,21 @@ +package net.contargo.iris.connection.api; + +import net.contargo.iris.connection.dto.RouteDto; + + +/** + * Service that serializes urls for routes. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public interface RouteUrlSerializationService { + + /** + * Constructs a link with the given {@link net.contargo.iris.connection.dto.RouteDto}'s detail information. + * + * @param route + * @param baseUrlRoute + * @param baseUrlRoutePart + */ + void serializeUrl(RouteDto route, String baseUrlRoute, String baseUrlRoutePart); +} diff --git a/src/main/java/net/contargo/iris/connection/api/RouteUrlSerializationServiceImpl.java b/src/main/java/net/contargo/iris/connection/api/RouteUrlSerializationServiceImpl.java new file mode 100644 index 00000000..84d3f7f8 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/api/RouteUrlSerializationServiceImpl.java @@ -0,0 +1,52 @@ +package net.contargo.iris.connection.api; + +import net.contargo.iris.connection.dto.RouteDto; + + +/** + * Default implementation of {@link RouteUrlSerializationService}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public class RouteUrlSerializationServiceImpl implements RouteUrlSerializationService { + + public static final String DATA_PARTS = "&data.parts["; + + @Override + public void serializeUrl(RouteDto route, String baseUrlRoute, String baseUrlRoutePart) { + + StringBuilder url = new StringBuilder(baseUrlRoute); + + url.append('?'); + url.append("terminal=").append(route.getResponsibleTerminal().getUniqueId()).append('&'); + + for (int i = 0; i < route.getData().getParts().size(); i++) { + if (i > 0) { + url.append('&'); + } + + url.append("data.parts[").append(i).append("].origin.longitude="); + url.append(route.getData().getParts().get(i).getOrigin().getLongitude().doubleValue()); + + url.append(DATA_PARTS).append(i).append("].origin.latitude="); + url.append(route.getData().getParts().get(i).getOrigin().getLatitude().doubleValue()); + + url.append(DATA_PARTS).append(i).append("].destination.longitude="); + url.append(route.getData().getParts().get(i).getDestination().getLongitude().doubleValue()); + + url.append(DATA_PARTS).append(i).append("].destination.latitude="); + url.append(route.getData().getParts().get(i).getDestination().getLatitude().doubleValue()); + + url.append(DATA_PARTS).append(i).append("].routeType="); + url.append(route.getData().getParts().get(i).getRouteType()); + + url.append(DATA_PARTS).append(i).append("].containerType="); + url.append(route.getData().getParts().get(i).getContainerType()); + + url.append(DATA_PARTS).append(i).append("].containerState="); + url.append(route.getData().getParts().get(i).getContainerState()); + } + + route.setUrl(url.toString()); + } +} diff --git a/src/main/java/net/contargo/iris/connection/api/RoutesResponse.java b/src/main/java/net/contargo/iris/connection/api/RoutesResponse.java new file mode 100644 index 00000000..d9aca288 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/api/RoutesResponse.java @@ -0,0 +1,27 @@ +package net.contargo.iris.connection.api; + +import net.contargo.iris.connection.dto.RouteDto; + +import org.springframework.hateoas.ResourceSupport; + +import java.util.List; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +class RoutesResponse extends ResourceSupport { + + private List routes; + + public List getRoutes() { + + return routes; + } + + + public void setRoutes(List routes) { + + this.routes = routes; + } +} diff --git a/src/main/java/net/contargo/iris/connection/api/SeaportsResponse.java b/src/main/java/net/contargo/iris/connection/api/SeaportsResponse.java new file mode 100644 index 00000000..88cec72c --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/api/SeaportsResponse.java @@ -0,0 +1,30 @@ +package net.contargo.iris.connection.api; + +import net.contargo.iris.seaport.dto.SeaportDto; + +import org.springframework.hateoas.ResourceSupport; + +import java.util.Set; + + +/** + * HATEOAS supporting response object for a list of {@link net.contargo.iris.seaport.dto.SeaportDto}s. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +class SeaportsResponse extends ResourceSupport { + + private Set seaports; + + public Set getSeaports() { + + return seaports; + } + + + public void setSeaports(Set seaports) { + + this.seaports = seaports; + } +} diff --git a/src/main/java/net/contargo/iris/connection/dto/GeolocationDtoFactory.java b/src/main/java/net/contargo/iris/connection/dto/GeolocationDtoFactory.java new file mode 100644 index 00000000..e73c48c5 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/GeolocationDtoFactory.java @@ -0,0 +1,49 @@ +package net.contargo.iris.connection.dto; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.Address; +import net.contargo.iris.address.dto.AddressDto; +import net.contargo.iris.address.dto.GeoLocationDto; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.seaport.dto.SeaportDto; +import net.contargo.iris.terminal.Terminal; +import net.contargo.iris.terminal.dto.TerminalDto; + + +/** + * Factory that creates {@link GeoLocationDto}s from {@link GeoLocation}s, {@link Terminal}s or {@link Seaport}s. + * + * @author Sandra Thieme - thieme@synyx.de + */ +final class GeolocationDtoFactory { + + private GeolocationDtoFactory() { + + // utility classes should not have a public or default constructor + } + + /** + * Transforms a {@link GeoLocation} instance into its corresponing dto of type {@link GeoLocationDto}, + * {@link TerminalDto} or {@link SeaportDto}. + * + * @param geolocation + * + * @return + */ + static GeoLocationDto createGeolocationDto(GeoLocation geolocation) { + + GeoLocationDto dto; + + if (geolocation instanceof Terminal) { + dto = new TerminalDto((Terminal) geolocation); + } else if (geolocation instanceof Seaport) { + dto = new SeaportDto((Seaport) geolocation); + } else if (geolocation instanceof Address) { + dto = new AddressDto((Address) geolocation); + } else { + dto = new GeoLocationDto(geolocation); + } + + return dto; + } +} diff --git a/src/main/java/net/contargo/iris/connection/dto/MainRunConnectionDto.java b/src/main/java/net/contargo/iris/connection/dto/MainRunConnectionDto.java new file mode 100644 index 00000000..a4cdf3d6 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/MainRunConnectionDto.java @@ -0,0 +1,43 @@ +package net.contargo.iris.connection.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import net.contargo.iris.route.RouteType; + + + +/** + * @author Oliver Messner - messner@synyx.de + */ +public class MainRunConnectionDto { + + private final String seaportUid; + private final String terminalUid; + private final RouteType routeType; + + public MainRunConnectionDto(String seaportUid, String terminalUid, RouteType routeType) { + + this.seaportUid = seaportUid; + this.terminalUid = terminalUid; + this.routeType = routeType; + } + + @JsonProperty("seaportUid") + public String getSeaportUid() { + + return seaportUid; + } + + + @JsonProperty("terminalUid") + public String getTerminalUid() { + + return terminalUid; + } + + + @JsonProperty("routeType") + public RouteType getRouteType() { + + return routeType; + } +} diff --git a/src/main/java/net/contargo/iris/connection/dto/MainRunConnectionDtoService.java b/src/main/java/net/contargo/iris/connection/dto/MainRunConnectionDtoService.java new file mode 100644 index 00000000..91eb269b --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/MainRunConnectionDtoService.java @@ -0,0 +1,14 @@ +package net.contargo.iris.connection.dto; + +import java.math.BigInteger; + +import java.util.List; + + +/** + * @author Oliver Messner - messner@synyx.de + */ +public interface MainRunConnectionDtoService { + + List getConnectionsForTerminal(BigInteger terminalUID); +} diff --git a/src/main/java/net/contargo/iris/connection/dto/MainRunConnectionDtoServiceImpl.java b/src/main/java/net/contargo/iris/connection/dto/MainRunConnectionDtoServiceImpl.java new file mode 100644 index 00000000..8838cba7 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/MainRunConnectionDtoServiceImpl.java @@ -0,0 +1,44 @@ +package net.contargo.iris.connection.dto; + +import net.contargo.iris.connection.MainRunConnection; +import net.contargo.iris.connection.service.MainRunConnectionService; + +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author Oliver Messner - messner@synyx.de + */ +public class MainRunConnectionDtoServiceImpl implements MainRunConnectionDtoService { + + private final MainRunConnectionService mainRunConnectionService; + + public MainRunConnectionDtoServiceImpl(MainRunConnectionService mainRunConnectionService) { + + this.mainRunConnectionService = mainRunConnectionService; + } + + @Override + public List getConnectionsForTerminal(BigInteger terminalUID) { + + List connectionDtos = new ArrayList<>(); + + for (MainRunConnection connection : mainRunConnectionService.getConnectionsForTerminal(terminalUID)) { + connectionDtos.add(newDto(connection)); + } + + return connectionDtos; + } + + + private MainRunConnectionDto newDto(MainRunConnection connection) { + + String seaportUid = connection.getSeaport().getUniqueId().toString(); + String terminalUid = connection.getTerminal().getUniqueId().toString(); + + return new MainRunConnectionDto(seaportUid, terminalUid, connection.getRouteType()); + } +} diff --git a/src/main/java/net/contargo/iris/connection/dto/RouteDataDto.java b/src/main/java/net/contargo/iris/connection/dto/RouteDataDto.java new file mode 100644 index 00000000..ebf78a57 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/RouteDataDto.java @@ -0,0 +1,170 @@ +package net.contargo.iris.connection.dto; + +import net.contargo.iris.route.RouteData; +import net.contargo.iris.route.RoutePart; + +import java.math.BigDecimal; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Dto layer for {@link RouteData}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public class RouteDataDto { + + private BigDecimal co2; + private BigDecimal co2DirectTruck; + private BigDecimal totalDistance; + private BigDecimal totalOnewayTruckDistance; + private BigDecimal totalRealTollDistance; + private BigDecimal totalTollDistance; + private BigDecimal totalDuration; + private List parts; + + public RouteDataDto() { + + // needed for Spring MVC instantiation of Controller parameter + } + + + public RouteDataDto(RouteData data) { + + parts = new ArrayList<>(); + + if (data != null) { + this.co2 = data.getCo2(); + this.co2DirectTruck = data.getCo2DirectTruck(); + this.totalDistance = data.getTotalDistance(); + this.totalDuration = data.getTotalDuration(); + this.totalOnewayTruckDistance = data.getTotalOnewayTruckDistance(); + this.totalRealTollDistance = data.getTotalRealTollDistance(); + this.totalTollDistance = data.getTotalTollDistance(); + + for (RoutePart part : data.getParts()) { + parts.add(new RoutePartDto(part)); + } + } + } + + public RouteData toRouteData() { + + RouteData routeData = new RouteData(); + routeData.setCo2(co2); + routeData.setCo2DirectTruck(co2DirectTruck); + routeData.setTotalDistance(totalDistance); + routeData.setTotalOnewayTruckDistance(totalOnewayTruckDistance); + routeData.setTotalRealTollDistance(totalRealTollDistance); + routeData.setTotalTollDistance(totalTollDistance); + routeData.setTotalDuration(totalDuration); + + List routeParts = new ArrayList<>(); + + for (RoutePartDto part : parts) { + routeParts.add(part.toRoutePart()); + } + + routeData.setParts(routeParts); + + return routeData; + } + + + public void setCo2(BigDecimal co2) { + + this.co2 = co2; + } + + + public void setCo2DirectTruck(BigDecimal co2DirectTruck) { + + this.co2DirectTruck = co2DirectTruck; + } + + + public void setTotalDistance(BigDecimal totalDistance) { + + this.totalDistance = totalDistance; + } + + + public void setTotalOnewayTruckDistance(BigDecimal totalOnewayTruckDistance) { + + this.totalOnewayTruckDistance = totalOnewayTruckDistance; + } + + + public void setTotalRealTollDistance(BigDecimal totalRealTollDistance) { + + this.totalRealTollDistance = totalRealTollDistance; + } + + + public void setTotalTollDistance(BigDecimal totalTollDistance) { + + this.totalTollDistance = totalTollDistance; + } + + + public void setTotalDuration(BigDecimal totalDuration) { + + this.totalDuration = totalDuration; + } + + + public void setParts(List parts) { + + this.parts = parts; + } + + + public BigDecimal getCo2() { + + return co2; + } + + + public BigDecimal getCo2DirectTruck() { + + return co2DirectTruck; + } + + + public List getParts() { + + return parts; + } + + + public BigDecimal getTotalDistance() { + + return totalDistance; + } + + + public BigDecimal getTotalOnewayTruckDistance() { + + return totalOnewayTruckDistance; + } + + + public BigDecimal getTotalRealTollDistance() { + + return totalRealTollDistance; + } + + + public BigDecimal getTotalTollDistance() { + + return totalTollDistance; + } + + + public BigDecimal getTotalDuration() { + + return totalDuration; + } +} diff --git a/src/main/java/net/contargo/iris/connection/dto/RouteDto.java b/src/main/java/net/contargo/iris/connection/dto/RouteDto.java new file mode 100644 index 00000000..79a83b39 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/RouteDto.java @@ -0,0 +1,181 @@ +package net.contargo.iris.connection.dto; + +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteDirection; +import net.contargo.iris.route.RouteProduct; +import net.contargo.iris.terminal.dto.TerminalDto; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +/** + * Dto layer for {@link Route}. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +public class RouteDto { + + private TerminalDto responsibleTerminal; + private boolean roundTrip; + private RouteProduct product; + private String shortName; + private String name; + private RouteDataDto data; + private Map errors; + private RouteDirection direction; + private String url; + + public RouteDto(Route route) { + + if (route != null) { + this.name = route.getName(); + this.data = route.getData() == null ? null : new RouteDataDto(route.getData()); + this.shortName = route.getShortName(); + this.roundTrip = route.isRoundTrip(); + this.direction = route.getDirection(); + this.product = route.getProduct(); + this.errors = route.getErrors(); + this.responsibleTerminal = route.getResponsibleTerminal() == null + ? null : new TerminalDto(route.getResponsibleTerminal()); + } + } + + + public RouteDto() { + + // needed for Spring MVC instantiation of Controller parameter + this.errors = new HashMap<>(); + } + + public Route toRoute() { + + Route route = new Route(); + + if (this.getData() != null) { + route.setData(this.getData().toRouteData()); + } + + if (responsibleTerminal != null) { + route.setResponsibleTerminal(this.responsibleTerminal.toEntity()); + } + + return route; + } + + + public TerminalDto getResponsibleTerminal() { + + return responsibleTerminal; + } + + + public void setResponsibleTerminal(TerminalDto responsibleTerminal) { + + this.responsibleTerminal = responsibleTerminal; + } + + + public int size() { + + return data.getParts().size(); + } + + + public void setName(String name) { + + this.name = name; + } + + + public void setRoundTrip(boolean roundTrip) { + + this.roundTrip = roundTrip; + } + + + public void setProduct(RouteProduct product) { + + this.product = product; + } + + + public void setShortName(String shortName) { + + this.shortName = shortName; + } + + + public void setData(RouteDataDto data) { + + this.data = data; + } + + + public void setErrors(Map errors) { + + this.errors = errors; + } + + + public void setDirection(RouteDirection direction) { + + this.direction = direction; + } + + + public String getName() { + + return name; + } + + + public String getShortName() { + + return shortName; + } + + + public RouteDataDto getData() { + + return data; + } + + + public boolean isRoundTrip() { + + return roundTrip; + } + + + public String getUrl() { + + return url; + } + + + public void setUrl(String url) { + + this.url = url; + } + + + public Map getErrors() { + + return Collections.unmodifiableMap(errors); + } + + + public RouteDirection getDirection() { + + return direction; + } + + + public RouteProduct getProduct() { + + return product; + } +} diff --git a/src/main/java/net/contargo/iris/connection/dto/RoutePartDataDto.java b/src/main/java/net/contargo/iris/connection/dto/RoutePartDataDto.java new file mode 100644 index 00000000..569c7e45 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/RoutePartDataDto.java @@ -0,0 +1,97 @@ +package net.contargo.iris.connection.dto; + +import net.contargo.iris.route.RoutePartData; + +import java.math.BigDecimal; + + +/** + * Dto for {@link RoutePartData}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public class RoutePartDataDto { + + private BigDecimal airlineDistance; + private BigDecimal distance; + private BigDecimal dieselDistance; + private BigDecimal electricDistance; + private BigDecimal tollDistance; + private BigDecimal duration; + private BigDecimal co2; + + public RoutePartDataDto() { + + // needed for Spring MVC instantiation of Controller parameter + } + + + public RoutePartDataDto(RoutePartData data) { + + if (data != null) { + this.airlineDistance = data.getAirLineDistance(); + this.distance = data.getDistance(); + this.dieselDistance = data.getDieselDistance(); + this.electricDistance = data.getElectricDistance(); + this.tollDistance = data.getTollDistance(); + this.duration = data.getDuration(); + this.co2 = data.getCo2(); + } + } + + public RoutePartData toRoutePartData() { + + RoutePartData routePartData = new RoutePartData(); + routePartData.setAirLineDistance(airlineDistance); + routePartData.setDistance(distance); + routePartData.setDieselDistance(dieselDistance); + routePartData.setElectricDistance(electricDistance); + routePartData.setTollDistance(tollDistance); + routePartData.setDuration(duration); + routePartData.setCo2(co2); + + return routePartData; + } + + + public BigDecimal getAirlineDistance() { + + return airlineDistance; + } + + + public BigDecimal getDistance() { + + return distance; + } + + + public BigDecimal getDieselDistance() { + + return dieselDistance; + } + + + public BigDecimal getElectricDistance() { + + return electricDistance; + } + + + public BigDecimal getTollDistance() { + + return tollDistance; + } + + + public BigDecimal getDuration() { + + return duration; + } + + + public BigDecimal getCo2() { + + return co2; + } +} diff --git a/src/main/java/net/contargo/iris/connection/dto/RoutePartDto.java b/src/main/java/net/contargo/iris/connection/dto/RoutePartDto.java new file mode 100644 index 00000000..27f8c6d4 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/RoutePartDto.java @@ -0,0 +1,157 @@ +package net.contargo.iris.connection.dto; + +import net.contargo.iris.address.dto.GeoLocationDto; +import net.contargo.iris.container.ContainerState; +import net.contargo.iris.container.ContainerType; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RouteType; + + +/** + * Dto layer for {@link RoutePart}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public class RoutePartDto { + + private String name; + private RoutePartDataDto data; + private GeoLocationDto origin; + private ContainerState containerState; + private ContainerType containerType; + private RoutePart.Direction direction; + private RouteType routeType; + private GeoLocationDto destination; + + public RoutePartDto() { + + // needed for Spring MVC instantiation of Controller parameter + } + + + public RoutePartDto(RoutePart part) { + + if (part != null) { + this.name = part.getName(); + this.containerState = part.getContainerState(); + this.containerType = part.getContainerType(); + this.direction = part.getDirection(); + this.routeType = part.getRouteType(); + this.data = part.getData() == null ? null : new RoutePartDataDto(part.getData()); + this.origin = GeolocationDtoFactory.createGeolocationDto(part.getOrigin()); + this.destination = GeolocationDtoFactory.createGeolocationDto(part.getDestination()); + } + } + + public RoutePart toRoutePart() { + + RoutePart routePart = new RoutePart(); + + if (this.origin != null && this.destination != null) { + routePart.setDestination(this.destination.toEntity()); + routePart.setOrigin(this.origin.toEntity()); + } + + routePart.setRouteType(this.routeType); + routePart.setContainerState(this.containerState); + routePart.setContainerType(this.containerType); + + return routePart; + } + + + public String getName() { + + return name; + } + + + public RoutePartDataDto getData() { + + return data; + } + + + public GeoLocationDto getOrigin() { + + return origin; + } + + + public ContainerState getContainerState() { + + return containerState; + } + + + public ContainerType getContainerType() { + + return containerType; + } + + + public RoutePart.Direction getDirection() { + + return direction; + } + + + public RouteType getRouteType() { + + return routeType; + } + + + public GeoLocationDto getDestination() { + + return destination; + } + + + public void setName(String name) { + + this.name = name; + } + + + public void setData(RoutePartDataDto data) { + + this.data = data; + } + + + public void setOrigin(GeoLocationDto origin) { + + this.origin = origin; + } + + + public void setContainerState(ContainerState containerState) { + + this.containerState = containerState; + } + + + public void setContainerType(ContainerType containerType) { + + this.containerType = containerType; + } + + + public void setDirection(RoutePart.Direction direction) { + + this.direction = direction; + } + + + public void setRouteType(RouteType routeType) { + + this.routeType = routeType; + } + + + public void setDestination(GeoLocationDto destination) { + + this.destination = destination; + } +} diff --git a/src/main/java/net/contargo/iris/connection/dto/SeaportConnectionRoutesDtoService.java b/src/main/java/net/contargo/iris/connection/dto/SeaportConnectionRoutesDtoService.java new file mode 100644 index 00000000..8a2dc9e2 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/SeaportConnectionRoutesDtoService.java @@ -0,0 +1,17 @@ +package net.contargo.iris.connection.dto; + +import net.contargo.iris.route.RouteInformation; +import net.contargo.iris.seaport.dto.SeaportDto; + +import java.util.List; + + +/** + * Dto interfact for {@link net.contargo.iris.connection.service.SeaportConnectionRoutesService}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public interface SeaportConnectionRoutesDtoService { + + List getAvailableSeaportConnectionRoutes(SeaportDto seaport, RouteInformation routeInformation); +} diff --git a/src/main/java/net/contargo/iris/connection/dto/SeaportConnectionRoutesDtoServiceImpl.java b/src/main/java/net/contargo/iris/connection/dto/SeaportConnectionRoutesDtoServiceImpl.java new file mode 100644 index 00000000..f3496e93 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/SeaportConnectionRoutesDtoServiceImpl.java @@ -0,0 +1,45 @@ +package net.contargo.iris.connection.dto; + +import net.contargo.iris.connection.service.SeaportConnectionRoutesService; +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteInformation; +import net.contargo.iris.seaport.dto.SeaportDto; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Default implementation of {@link SeaportConnectionRoutesDtoService}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public class SeaportConnectionRoutesDtoServiceImpl implements SeaportConnectionRoutesDtoService { + + private final SeaportConnectionRoutesService seaportConnectionRoutesService; + + public SeaportConnectionRoutesDtoServiceImpl(SeaportConnectionRoutesService seaportConnectionRoutesService) { + + this.seaportConnectionRoutesService = seaportConnectionRoutesService; + } + + @Override + public List getAvailableSeaportConnectionRoutes(SeaportDto seaportDto, + RouteInformation routeInformation) { + + List routeDtos = new ArrayList<>(); + + if (seaportDto == null) { + return routeDtos; + } + + List routes = seaportConnectionRoutesService.getAvailableSeaportConnectionRoutes(seaportDto.toEntity(), + routeInformation); + + for (Route route : routes) { + routeDtos.add(new RouteDto(route)); + } + + return routeDtos; + } +} diff --git a/src/main/java/net/contargo/iris/connection/dto/SeaportTerminalConnectionDtoService.java b/src/main/java/net/contargo/iris/connection/dto/SeaportTerminalConnectionDtoService.java new file mode 100644 index 00000000..9fa2642f --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/SeaportTerminalConnectionDtoService.java @@ -0,0 +1,41 @@ +package net.contargo.iris.connection.dto; + +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.dto.SeaportDto; +import net.contargo.iris.terminal.dto.TerminalDto; + +import java.util.List; + + +/** + * Dto service layer for {@link net.contargo.iris.connection.service.SeaportTerminalConnectionService}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public interface SeaportTerminalConnectionDtoService { + + /** + * Finds all {@link net.contargo.iris.seaport.Seaport}s that have a + * {@link net.contargo.iris.connection.MainRunConnection} of type {@code type}. + * + * @param type the {@link net.contargo.iris.connection.MainRunConnection}'s + * {@link net.contargo.iris.route.RouteType} + * + * @return all matching {@link net.contargo.iris.seaport.Seaport}s as {@link SeaportDto}s + */ + List findSeaportsConnectedByRouteType(RouteType type); + + + /** + * Finds all {@link net.contargo.iris.terminal.Terminal}s that are part of a + * {@link net.contargo.iris.connection.MainRunConnection} with the given {@link RouteType} and the specified + * {@link net.contargo.iris.seaport.Seaport} property. + * + * @param seaport the {@link net.contargo.iris.connection.MainRunConnection}'s + * {@link net.contargo.iris.seaport.Seaport} + * @param routeType the {@link net.contargo.iris.connection.MainRunConnection}'s {@link RouteType} + * + * @return all matching {@link net.contargo.iris.terminal.Terminal}s as {@link TerminalDto}s + */ + List findTerminalsConnectedToSeaPortByRouteType(SeaportDto seaport, RouteType routeType); +} diff --git a/src/main/java/net/contargo/iris/connection/dto/SeaportTerminalConnectionDtoServiceImpl.java b/src/main/java/net/contargo/iris/connection/dto/SeaportTerminalConnectionDtoServiceImpl.java new file mode 100644 index 00000000..d21144b1 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/dto/SeaportTerminalConnectionDtoServiceImpl.java @@ -0,0 +1,59 @@ +package net.contargo.iris.connection.dto; + +import net.contargo.iris.connection.service.SeaportTerminalConnectionService; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.seaport.dto.SeaportDto; +import net.contargo.iris.terminal.Terminal; +import net.contargo.iris.terminal.dto.TerminalDto; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Implementation of {@link SeaportTerminalConnectionDtoService}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public class SeaportTerminalConnectionDtoServiceImpl implements SeaportTerminalConnectionDtoService { + + private final SeaportTerminalConnectionService seaportTerminalConnectionService; + + public SeaportTerminalConnectionDtoServiceImpl(SeaportTerminalConnectionService seaportTerminalConnectionService) { + + this.seaportTerminalConnectionService = seaportTerminalConnectionService; + } + + @Override + public List findSeaportsConnectedByRouteType(RouteType type) { + + List seaports = seaportTerminalConnectionService.findSeaPortsConnectedByRouteType(type); + + List dtos = new ArrayList<>(); + + for (Seaport seaport : seaports) { + dtos.add(new SeaportDto(seaport)); + } + + return dtos; + } + + + @Override + public List findTerminalsConnectedToSeaPortByRouteType(SeaportDto seaportDto, RouteType routeType) { + + Seaport seaport = seaportDto.toEntity(); + + List terminals = seaportTerminalConnectionService.getTerminalsConnectedToSeaPortByRouteType(seaport, + routeType); + + List dtos = new ArrayList<>(); + + for (Terminal terminal : terminals) { + dtos.add(new TerminalDto(terminal)); + } + + return dtos; + } +} diff --git a/src/main/java/net/contargo/iris/connection/persistence/MainRunConnectionRepository.java b/src/main/java/net/contargo/iris/connection/persistence/MainRunConnectionRepository.java new file mode 100644 index 00000000..0dec7b54 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/persistence/MainRunConnectionRepository.java @@ -0,0 +1,110 @@ +package net.contargo.iris.connection.persistence; + +import net.contargo.iris.connection.MainRunConnection; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.math.BigInteger; + +import java.util.List; + + +/** + * Repository interface for {@link net.contargo.iris.connection.MainRunConnection}s that grants access to the database. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public interface MainRunConnectionRepository extends JpaRepository { + + /** + * Finds all {@link net.contargo.iris.connection.MainRunConnection}s with the specified {@code enabled} value. + * + * @param enabled indicates whether enabled or non-enabled {@link net.contargo.iris.connection.MainRunConnection}s + * should be returned + * + * @return a list of {@link net.contargo.iris.connection.MainRunConnection}s + */ + List findByEnabled(boolean enabled); + + + /** + * Finds the specified {@link net.contargo.iris.connection.MainRunConnection}. + * + * @param terminal the {@link net.contargo.iris.connection.MainRunConnection}'s {@link Terminal} + * @param seaport the {@link net.contargo.iris.connection.MainRunConnection}'s {@link Seaport} + * @param routeType the {@link net.contargo.iris.connection.MainRunConnection}'s {@link RouteType} + * @param enabled indicates whether the connection should be enabled or not + * + * @return a {@link net.contargo.iris.connection.MainRunConnection} with the specified properties + */ + MainRunConnection findByTerminalAndSeaportAndRouteTypeAndEnabled(Terminal terminal, Seaport seaport, + RouteType routeType, boolean enabled); + + + /** + * Finds all {@link Seaport}s that are part of a {@link net.contargo.iris.connection.MainRunConnection} with the + * given {@link RouteType}. + * + * @param type the {@link net.contargo.iris.connection.MainRunConnection}'s {@link RouteType} + * + * @return a list of {@link Seaport}s + */ + @Query( + "SELECT DISTINCT p FROM MainRunConnection c JOIN c.seaport p WHERE c.routeType = ?1 and p.enabled = true " + + "and c.enabled = true" + ) + List findSeaportsConnectedByRouteType(RouteType type); + + + /** + * Finds all {@link Terminal}s that are part of a {@link net.contargo.iris.connection.MainRunConnection} with the + * given {@link RouteType} and the specified {@link Seaport} property. + * + * @param seaportUid the {@link net.contargo.iris.connection.MainRunConnection}'s {@link Seaport}'s unique id + * @param routeType the {@link net.contargo.iris.connection.MainRunConnection}'s {@link RouteType} + * + * @return a list of {@link Terminal}s + */ + @Query( + "SELECT t FROM MainRunConnection c JOIN c.terminal t JOIN c.seaport s WHERE c.seaport.uniqueId = ?1 " + + "AND c.routeType = ?2 and t.enabled = true and s.enabled = true and c.enabled = true" + ) + List getTerminalsConnectedToSeaPortByRouteType(BigInteger seaportUid, RouteType routeType); + + + /** + * Checks if a {@link net.contargo.iris.connection.MainRunConnection} by {@link Seaport}, {@link Terminal} and + * {@link RouteType} without the own id is already applied. + * + * @param seaport the {@link net.contargo.iris.connection.MainRunConnection}'s {@link Seaport} + * @param terminal the {@link net.contargo.iris.connection.MainRunConnection}'s {@link Terminal} + * @param routeType the {@link net.contargo.iris.connection.MainRunConnection}'s {@link RouteType} + * @param id the id of the own {@link net.contargo.iris.connection.MainRunConnection} + * + * @return true, if a connection is not found or is found but with the given id + */ + MainRunConnection findBySeaportAndTerminalAndRouteTypeAndIdNot(Seaport seaport, Terminal terminal, + RouteType routeType, Long id); + + + /** + * Checks if a {@link net.contargo.iris.connection.MainRunConnection} by {@link Seaport}, {@link Terminal} and + * {@link RouteType}is already applied. + * + * @param seaport the {@link net.contargo.iris.connection.MainRunConnection}'s {@link Seaport} + * @param terminal the {@link net.contargo.iris.connection.MainRunConnection}'s {@link Terminal} + * @param routeType the {@link net.contargo.iris.connection.MainRunConnection}'s {@link RouteType} + * + * @return true, if no connection with this attributes are found + */ + MainRunConnection findBySeaportAndTerminalAndRouteType(Seaport seaport, Terminal terminal, RouteType routeType); + + + @Query("SELECT c FROM MainRunConnection c WHERE c.terminal.uniqueId = ?1") + List findConnectionsByTerminalUniqueId(BigInteger terminalUID); +} diff --git a/src/main/java/net/contargo/iris/connection/service/MainRunConnectionService.java b/src/main/java/net/contargo/iris/connection/service/MainRunConnectionService.java new file mode 100644 index 00000000..60be3e29 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/service/MainRunConnectionService.java @@ -0,0 +1,97 @@ +package net.contargo.iris.connection.service; + +import net.contargo.iris.connection.MainRunConnection; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + +import java.math.BigInteger; + +import java.util.List; + + +/** + * Services that provides handling of {@link net.contargo.iris.connection.MainRunConnection}s. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Tobias Schneider - schneider@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public interface MainRunConnectionService { + + /** + * Finds all {@link net.contargo.iris.connection.MainRunConnection}s. + * + * @return a list of {@link net.contargo.iris.connection.MainRunConnection}s + */ + List getAll(); + + + /** + * Finds all active, i.e. enabled {@link net.contargo.iris.connection.MainRunConnection}s + * + * @return a list of {@link net.contargo.iris.connection.MainRunConnection}s + */ + List getAllActive(); + + + /** + * Finds a {@link net.contargo.iris.connection.MainRunConnection}. + * + * @param id the {@link net.contargo.iris.connection.MainRunConnection}'s id + * + * @return the {@link net.contargo.iris.connection.MainRunConnection} with the specified {@code id} + */ + MainRunConnection getById(Long id); + + + /** + * Saves a {@link net.contargo.iris.connection.MainRunConnection}. + * + * @param mainrunConnection the {@link net.contargo.iris.connection.MainRunConnection} to be saved + * + * @return the saved {MainRunConnection} that does not have a null id property + */ + MainRunConnection save(MainRunConnection mainrunConnection); + + + /** + * Finds a {@link net.contargo.iris.connection.MainRunConnection} with the specified attributes. + * + * @param terminal the {@link net.contargo.iris.connection.MainRunConnection}'s {@link Terminal} + * @param seaport the {@link net.contargo.iris.connection.MainRunConnection}'s {@link Seaport} + * @param routeType the {@link net.contargo.iris.connection.MainRunConnection}'s {@link RouteType} + * + * @return a {@link net.contargo.iris.connection.MainRunConnection} + */ + MainRunConnection findRoutingConnectionBetweenTerminalAndSeaportByType(Terminal terminal, Seaport seaport, + RouteType routeType); + + + /** + * Checks if a connection between a {@link Seaport}, {@link Terminal} and {@link RouteType} is already applied. + * + * @param mainrunConnection Keeps the information of the {@link net.contargo.iris.seaport.Seaport}, + * {@link net.contargo.iris.terminal.Terminal}, and + * {@link net.contargo.iris.route.RouteType} + * + * @return true, if the connection is already applied + */ + Boolean isAlreadyApplied(MainRunConnection mainrunConnection); + + + /** + * Checks if a connection, and not the given connection, between a {@link Seaport}, {@link Terminal} and + * {@link RouteType} is already applied. + * + * @param mainrunConnection Keeps the information of the {@link net.contargo.iris.seaport.Seaport}, + * {@link net.contargo.iris.terminal.Terminal}, and + * {@link net.contargo.iris.route.RouteType} + * + * @return true, if the connection is already applied but is not the own connection + */ + Boolean isAlreadyAppliedAndNotThis(MainRunConnection mainrunConnection); + + + List getConnectionsForTerminal(BigInteger terminalUID); +} diff --git a/src/main/java/net/contargo/iris/connection/service/MainRunConnectionServiceImpl.java b/src/main/java/net/contargo/iris/connection/service/MainRunConnectionServiceImpl.java new file mode 100644 index 00000000..21d21755 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/service/MainRunConnectionServiceImpl.java @@ -0,0 +1,125 @@ +package net.contargo.iris.connection.service; + +import net.contargo.iris.connection.MainRunConnection; +import net.contargo.iris.connection.persistence.MainRunConnectionRepository; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + +import org.springframework.transaction.annotation.Transactional; + +import org.springframework.util.Assert; + +import java.math.BigInteger; + +import java.util.List; + + +/** + * Service for operations with the {@link net.contargo.iris.connection.MainRunConnection}. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Tobias Schneider - schneider@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +@Transactional +public class MainRunConnectionServiceImpl implements MainRunConnectionService { + + private final MainRunConnectionRepository mainRunConnectionRepository; + + public MainRunConnectionServiceImpl(MainRunConnectionRepository mainRunConnectionRepository) { + + this.mainRunConnectionRepository = mainRunConnectionRepository; + } + + /** + * @see MainRunConnectionService#getAll() + */ + @Override + public List getAll() { + + return mainRunConnectionRepository.findAll(); + } + + + /** + * @see MainRunConnectionService#getAllActive() + */ + @Override + public List getAllActive() { + + return mainRunConnectionRepository.findByEnabled(true); + } + + + /** + * @see MainRunConnectionService#getById(Long) + */ + @Override + public MainRunConnection getById(Long id) { + + return mainRunConnectionRepository.findOne(id); + } + + + /** + * @see MainRunConnectionService#save(net.contargo.iris.connection.MainRunConnection) + */ + @Override + public MainRunConnection save(MainRunConnection mainrunConnection) { + + return mainRunConnectionRepository.save(mainrunConnection); + } + + + /** + * @see MainRunConnectionService#findRoutingConnectionBetweenTerminalAndSeaportByType( + * net.contargo.iris.terminal.Terminal, net.contargo.iris.seaport.Seaport, net.contargo.iris.route.RouteType) + */ + @Override + public MainRunConnection findRoutingConnectionBetweenTerminalAndSeaportByType(Terminal terminal, Seaport seaport, + RouteType routeType) { + + Assert.notNull(terminal); + Assert.notNull(seaport); + Assert.notNull(routeType); + + return mainRunConnectionRepository.findByTerminalAndSeaportAndRouteTypeAndEnabled(terminal, seaport, routeType, + true); + } + + + /** + * @see MainRunConnectionService#isAlreadyApplied(net.contargo.iris.connection.MainRunConnection) + */ + @Override + public Boolean isAlreadyApplied(MainRunConnection mainrunConnection) { + + MainRunConnection dbMainrunConnection = mainRunConnectionRepository.findBySeaportAndTerminalAndRouteType( + mainrunConnection.getSeaport(), mainrunConnection.getTerminal(), mainrunConnection.getRouteType()); + + return null != dbMainrunConnection; + } + + + /** + * @see MainRunConnectionService#isAlreadyAppliedAndNotThis(net.contargo.iris.connection.MainRunConnection) + */ + @Override + public Boolean isAlreadyAppliedAndNotThis(MainRunConnection mainrunConnection) { + + MainRunConnection dbMainrunConnection = + mainRunConnectionRepository.findBySeaportAndTerminalAndRouteTypeAndIdNot(mainrunConnection.getSeaport(), + mainrunConnection.getTerminal(), mainrunConnection.getRouteType(), mainrunConnection.getId()); + + return null != dbMainrunConnection; + } + + + @Override + @Transactional(readOnly = true) + public List getConnectionsForTerminal(BigInteger terminalUID) { + + return mainRunConnectionRepository.findConnectionsByTerminalUniqueId(terminalUID); + } +} diff --git a/src/main/java/net/contargo/iris/connection/service/SeaportConnectionRoutesService.java b/src/main/java/net/contargo/iris/connection/service/SeaportConnectionRoutesService.java new file mode 100644 index 00000000..5245d6a9 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/service/SeaportConnectionRoutesService.java @@ -0,0 +1,27 @@ +package net.contargo.iris.connection.service; + +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteInformation; +import net.contargo.iris.seaport.Seaport; + +import java.util.List; + + +/** + * Service that calculates {@link Route}s between {@link Seaport}s and some destination. + * + * @author Sven Mueller - mueller@synyx.de + * @author Sandra Thieme - thieme@synyx.de + */ +public interface SeaportConnectionRoutesService { + + /** + * Returns a list of all possible connection routes between a seaport and a destination address. + * + * @param origin The origin seaport + * @param routeInformation + * + * @return List of possible connections (empty if none found) + */ + List getAvailableSeaportConnectionRoutes(Seaport origin, RouteInformation routeInformation); +} diff --git a/src/main/java/net/contargo/iris/connection/service/SeaportConnectionRoutesServiceImpl.java b/src/main/java/net/contargo/iris/connection/service/SeaportConnectionRoutesServiceImpl.java new file mode 100644 index 00000000..083aa01e --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/service/SeaportConnectionRoutesServiceImpl.java @@ -0,0 +1,63 @@ +package net.contargo.iris.connection.service; + +import net.contargo.iris.connection.advice.MainRunAdvisor; +import net.contargo.iris.connection.advice.MainRunStrategy; +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteInformation; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +public class SeaportConnectionRoutesServiceImpl implements SeaportConnectionRoutesService { + + private final SeaportTerminalConnectionService seaportTerminalConnectionService; + private final MainRunAdvisor mainRunAdvisor; + + public SeaportConnectionRoutesServiceImpl(SeaportTerminalConnectionService seaportTerminalConnectionService, + MainRunAdvisor mainRunAdvisor) { + + this.seaportTerminalConnectionService = seaportTerminalConnectionService; + this.mainRunAdvisor = mainRunAdvisor; + } + + Route getMainRunRoute(Seaport seaPort, Terminal terminal, RouteInformation routeInformation, RouteType routeType) { + + if (routeType != RouteType.BARGE && routeType != RouteType.RAIL) { + throw new IllegalArgumentException(); + } + + MainRunStrategy mainRunStrategy = mainRunAdvisor.advice(routeInformation.getProduct(), + routeInformation.getRouteDirection()); + + return mainRunStrategy.getRoute(seaPort, routeInformation.getDestination(), terminal, + routeInformation.getContainerType(), routeType); + } + + + @Override + public List getAvailableSeaportConnectionRoutes(Seaport origin, RouteInformation routeInformation) { + + List routes = new ArrayList<>(); + + // a RouteCombo has one ore more (in case RouteCombo.ALL) RouteTypes + for (RouteType routeType : routeInformation.getRouteCombo().getRouteTypes()) { + // get all terminals that have routes with the given route type to the given seaport + List terminals = seaportTerminalConnectionService.getTerminalsConnectedToSeaPortByRouteType( + origin, routeType); + + // get route for each location and add it to route list + for (Terminal terminal : terminals) { + routes.add(getMainRunRoute(origin, terminal, routeInformation, routeType)); + } + } + + return routes; + } +} diff --git a/src/main/java/net/contargo/iris/connection/service/SeaportTerminalConnectionService.java b/src/main/java/net/contargo/iris/connection/service/SeaportTerminalConnectionService.java new file mode 100644 index 00000000..3291acb7 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/service/SeaportTerminalConnectionService.java @@ -0,0 +1,38 @@ +package net.contargo.iris.connection.service; + +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + +import java.util.List; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +public interface SeaportTerminalConnectionService { + + /** + * Finds all {@link net.contargo.iris.seaport.Seaport}s that have a + * {@link net.contargo.iris.connection.MainRunConnection} of type {@code type}. + * + * @param type the {@link net.contargo.iris.connection.MainRunConnection}'s + * {@link net.contargo.iris.route.RouteType} + * + * @return all matching {@link net.contargo.iris.seaport.Seaport}s + */ + List findSeaPortsConnectedByRouteType(RouteType type); + + + /** + * Finds all {@link net.contargo.iris.terminal.Terminal}s that are part of a + * {@link net.contargo.iris.connection.MainRunConnection} with the given {@link RouteType} and the specified + * {@link Seaport} property. + * + * @param seaPort the {@link net.contargo.iris.connection.MainRunConnection}'s {@link Seaport} + * @param routeType the {@link net.contargo.iris.connection.MainRunConnection}'s {@link RouteType} + * + * @return all matching {@link net.contargo.iris.terminal.Terminal}s + */ + List getTerminalsConnectedToSeaPortByRouteType(Seaport seaPort, RouteType routeType); +} diff --git a/src/main/java/net/contargo/iris/connection/service/SeaportTerminalConnectionServiceImpl.java b/src/main/java/net/contargo/iris/connection/service/SeaportTerminalConnectionServiceImpl.java new file mode 100644 index 00000000..1caa1132 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/service/SeaportTerminalConnectionServiceImpl.java @@ -0,0 +1,56 @@ +package net.contargo.iris.connection.service; + +import net.contargo.iris.connection.persistence.MainRunConnectionRepository; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.seaport.service.SeaportService; +import net.contargo.iris.terminal.Terminal; +import net.contargo.iris.terminal.service.TerminalService; + +import java.util.List; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +public class SeaportTerminalConnectionServiceImpl implements SeaportTerminalConnectionService { + + private final MainRunConnectionRepository mainRunConnectionRepository; + private final SeaportService seaportService; + private final TerminalService terminalService; + + public SeaportTerminalConnectionServiceImpl(MainRunConnectionRepository mainRunConnectionRepository, + SeaportService seaportService, TerminalService terminalService) { + + this.mainRunConnectionRepository = mainRunConnectionRepository; + this.seaportService = seaportService; + this.terminalService = terminalService; + } + + /** + * @see SeaportTerminalConnectionService#findSeaPortsConnectedByRouteType(net.contargo.iris.route.RouteType) + */ + @Override + public List findSeaPortsConnectedByRouteType(RouteType type) { + + if (type.equals(RouteType.TRUCK)) { + return seaportService.getAllActive(); + } + + return mainRunConnectionRepository.findSeaportsConnectedByRouteType(type); + } + + + /** + * @see SeaportTerminalConnectionService#getTerminalsConnectedToSeaPortByRouteType(Seaport, RouteType) + */ + @Override + public List getTerminalsConnectedToSeaPortByRouteType(Seaport seaPort, RouteType routeType) { + + if (routeType.equals(RouteType.TRUCK)) { + return terminalService.getAllActive(); + } + + return mainRunConnectionRepository.getTerminalsConnectedToSeaPortByRouteType(seaPort.getUniqueId(), routeType); + } +} diff --git a/src/main/java/net/contargo/iris/connection/web/MainRunConnectionController.java b/src/main/java/net/contargo/iris/connection/web/MainRunConnectionController.java new file mode 100644 index 00000000..bf705286 --- /dev/null +++ b/src/main/java/net/contargo/iris/connection/web/MainRunConnectionController.java @@ -0,0 +1,163 @@ +package net.contargo.iris.connection.web; + +import net.contargo.iris.Message; +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.connection.MainRunConnection; +import net.contargo.iris.connection.service.MainRunConnectionService; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.service.SeaportService; +import net.contargo.iris.terminal.service.TerminalService; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.ui.Model; + +import org.springframework.validation.BindingResult; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import java.util.Arrays; + +import javax.validation.Valid; + +import static net.contargo.iris.api.AbstractController.CONNECTIONS; +import static net.contargo.iris.api.AbstractController.SLASH; + + +/** + * Controller to handle {@link net.contargo.iris.connection.MainRunConnection}'s. + * + * @author Oliver Messner - messner@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@Controller +@RequestMapping(SLASH + CONNECTIONS) +public class MainRunConnectionController extends AbstractController { + + private static final String CONTROLLER_CONTEXT = "connectionManagement" + SLASH; + + private static final String SEAPORTS_ATTRIBUTE = SEAPORTS; + private static final String TERMINALS_ATTRIBUTE = TERMINALS; + private static final String ROUTE_TYPES_ATTRIBUTE = ROUTE_TYPES; + + private static final String MAINRUN_CONNECTIONS_ATTRIBUTE = "mainRunConnections"; + private static final String MAINRUN_CONNECTION_ATTRIBUTE = "mainRunConnection"; + + private static final String MAINRUN_CONNECTIONS_VIEW = "mainrunconnections"; + private static final String MAINRUN_CONNECTION_VIEW = "mainrunconnection"; + + private static final Message SAVE_SUCCESS_MESSAGE = Message.success("mainrunconnection.success.save.message"); + private static final Message EDIT_SUCCESS_MESSAGE = Message.success("mainrunconnection.success.edit.message"); + + private final MainRunConnectionService mainRunConnectionService; + private final TerminalService terminalService; + private final SeaportService seaportService; + + @Autowired + public MainRunConnectionController(MainRunConnectionService mainRunConnectionService, + TerminalService terminalService, SeaportService seaportService) { + + this.mainRunConnectionService = mainRunConnectionService; + this.terminalService = terminalService; + this.seaportService = seaportService; + } + + @RequestMapping(method = RequestMethod.GET) + public String getAllConnections(Model model) { + + model.addAttribute(MAINRUN_CONNECTIONS_ATTRIBUTE, mainRunConnectionService.getAll()); + + return CONTROLLER_CONTEXT + MAINRUN_CONNECTIONS_VIEW; + } + + + @RequestMapping(value = SLASH + "new", method = RequestMethod.GET) + public String prepareForCreate(Model model) { + + model.addAttribute(MAINRUN_CONNECTION_ATTRIBUTE, new MainRunConnection()); + addCollectionsForDropdowns(model); + + return CONTROLLER_CONTEXT + MAINRUN_CONNECTION_VIEW; + } + + + @RequestMapping(value = SLASH, method = RequestMethod.POST) + public String createConnection(@Valid @ModelAttribute MainRunConnection mainRunConnection, BindingResult result, + Model model, RedirectAttributes redirectAttributes) { + + if (mainRunConnectionService.isAlreadyApplied(mainRunConnection)) { + model.addAttribute(MAINRUN_CONNECTION_ATTRIBUTE, mainRunConnection); + addCollectionsForDropdowns(model); + + result.rejectValue("seaport.id", "mainrunconnection.duplicate.connection.seaport"); + result.rejectValue("terminal.id", "mainrunconnection.duplicate.connection.terminal"); + result.rejectValue("routeType", "mainrunconnection.duplicate.connection.routetype"); + + return CONTROLLER_CONTEXT + MAINRUN_CONNECTION_VIEW; + } + + return createOrUpdate(model, result, mainRunConnection, redirectAttributes, SAVE_SUCCESS_MESSAGE); + } + + + @RequestMapping(value = SLASH + ID_PARAM, method = RequestMethod.GET) + public String prepareForUpdate(@PathVariable Long id, Model model) { + + model.addAttribute(MAINRUN_CONNECTION_ATTRIBUTE, mainRunConnectionService.getById(id)); + addCollectionsForDropdowns(model); + + return CONTROLLER_CONTEXT + MAINRUN_CONNECTION_VIEW; + } + + + @RequestMapping(value = SLASH + ID_PARAM, method = RequestMethod.PUT) + public String updateConnection(@Valid @ModelAttribute MainRunConnection mainRunConnection, BindingResult result, + @PathVariable Long id, Model model, RedirectAttributes redirectAttributes) { + + mainRunConnection.setId(mainRunConnectionService.getById(id).getId()); + + if (mainRunConnectionService.isAlreadyAppliedAndNotThis(mainRunConnection)) { + model.addAttribute(MAINRUN_CONNECTION_ATTRIBUTE, mainRunConnection); + addCollectionsForDropdowns(model); + + result.rejectValue("seaport.id", "mainrunconnection.duplicate.connection.seaport"); + result.rejectValue("terminal.id", "mainrunconnection.duplicate.connection.terminal"); + result.rejectValue("routeType", "mainrunconnection.duplicate.connection.routetype"); + + return CONTROLLER_CONTEXT + MAINRUN_CONNECTION_VIEW; + } + + return createOrUpdate(model, result, mainRunConnection, redirectAttributes, EDIT_SUCCESS_MESSAGE); + } + + + private String createOrUpdate(Model model, BindingResult result, MainRunConnection mainRunConnection, + RedirectAttributes redirectAttributes, Message successMessage) { + + if (result.hasErrors()) { + addCollectionsForDropdowns(model); + + return CONTROLLER_CONTEXT + MAINRUN_CONNECTION_VIEW; + } + + Long id = mainRunConnectionService.save(mainRunConnection).getId(); + + redirectAttributes.addFlashAttribute(MESSAGE, successMessage); + + return REDIRECT + WEBAPI_ROOT_URL + CONNECTIONS + SLASH + id; + } + + + private void addCollectionsForDropdowns(Model model) { + + model.addAttribute(SEAPORTS_ATTRIBUTE, seaportService.getAll()); + model.addAttribute(TERMINALS_ATTRIBUTE, terminalService.getAll()); + model.addAttribute(ROUTE_TYPES_ATTRIBUTE, Arrays.asList(RouteType.BARGE, RouteType.RAIL)); + } +} diff --git a/src/main/java/net/contargo/iris/container/ContainerState.java b/src/main/java/net/contargo/iris/container/ContainerState.java new file mode 100644 index 00000000..c3e42ca3 --- /dev/null +++ b/src/main/java/net/contargo/iris/container/ContainerState.java @@ -0,0 +1,11 @@ +package net.contargo.iris.container; + +/** + * @author Aljona Murygina - murygina@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public enum ContainerState { + + FULL, + EMPTY +} diff --git a/src/main/java/net/contargo/iris/container/ContainerType.java b/src/main/java/net/contargo/iris/container/ContainerType.java new file mode 100644 index 00000000..fdb8f0a3 --- /dev/null +++ b/src/main/java/net/contargo/iris/container/ContainerType.java @@ -0,0 +1,35 @@ +package net.contargo.iris.container; + +/** + * Enum for type of containers. + * + * @author Aljona Murygina - murygina@synyx.de + */ + +public enum ContainerType { + + NOT_SET(""), + TWENTY_LIGHT("twenty_light"), + TWENTY_HEAVY("twenty_heavy"), + THIRTY(""), + FORTY("forty"), + FORTYFIVE(""); + + private String csvString; + + private ContainerType(String csvString) { + + this.csvString = csvString; + } + + public boolean isUnknown() { + + return this.equals(NOT_SET); + } + + + public String getCsvString() { + + return this.csvString; + } +} diff --git a/src/main/java/net/contargo/iris/countries/api/CountriesApiController.java b/src/main/java/net/contargo/iris/countries/api/CountriesApiController.java new file mode 100644 index 00000000..deaffe5b --- /dev/null +++ b/src/main/java/net/contargo/iris/countries/api/CountriesApiController.java @@ -0,0 +1,52 @@ +package net.contargo.iris.countries.api; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; + +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.countries.dto.CountryDtoService; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + + +/** + * API Controller for Information about countries. + * + * @author Arnold Franke - franke@synyx.de + * @author David Schilling - schilling@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +@Controller +@RequestMapping(value = CountriesApiController.COUNTRIES) +@Api(value = CountriesApiController.COUNTRIES, description = "API for Information about countries.") +public class CountriesApiController extends AbstractController { + + private final CountryDtoService countryDtoService; + + @Autowired + public CountriesApiController(CountryDtoService countryDtoService) { + + this.countryDtoService = countryDtoService; + } + + @ApiOperation(value = "Get all countries.", notes = "Get all countries.") + @ModelAttribute("countriesResponse") + @RequestMapping(method = RequestMethod.GET) + public CountriesResponse countries() { + + CountriesResponse response = new CountriesResponse(); + response.setCountries(countryDtoService.getCountries()); + + response.add(linkTo(getClass()).withSelfRel()); + + return response; + } +} diff --git a/src/main/java/net/contargo/iris/countries/api/CountriesResponse.java b/src/main/java/net/contargo/iris/countries/api/CountriesResponse.java new file mode 100644 index 00000000..0cf648c3 --- /dev/null +++ b/src/main/java/net/contargo/iris/countries/api/CountriesResponse.java @@ -0,0 +1,29 @@ +package net.contargo.iris.countries.api; + +import net.contargo.iris.countries.dto.CountryDto; + +import org.springframework.hateoas.ResourceSupport; + +import java.util.List; + + +/** + * @author Aljona Murygina - murygina@synyx.de + * @author Arnold Franke - franke@synyx.de + * @author David Schilling - schilling@synyx.de + */ +class CountriesResponse extends ResourceSupport { + + private List countries; + + public List getCountries() { + + return countries; + } + + + public void setCountries(List countries) { + + this.countries = countries; + } +} diff --git a/src/main/java/net/contargo/iris/countries/dto/CountryCodeCountryDtoService.java b/src/main/java/net/contargo/iris/countries/dto/CountryCodeCountryDtoService.java new file mode 100644 index 00000000..6364cefb --- /dev/null +++ b/src/main/java/net/contargo/iris/countries/dto/CountryCodeCountryDtoService.java @@ -0,0 +1,35 @@ +package net.contargo.iris.countries.dto; + +import net.contargo.iris.countries.service.CountryService; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +/** + * @author Arnold Franke - franke@synyx.de + * + *

Created: Date: 8/23/13 Time: 8:57 AM

+ */ +public class CountryCodeCountryDtoService implements CountryDtoService { + + private final CountryService service; + + public CountryCodeCountryDtoService(CountryService service) { + + this.service = service; + } + + @Override + public List getCountries() { + + List countryDtoList = new ArrayList<>(); + + for (Map.Entry entry : service.getCountries().entrySet()) { + countryDtoList.add(new CountryDto(entry.getKey(), entry.getValue())); + } + + return countryDtoList; + } +} diff --git a/src/main/java/net/contargo/iris/countries/dto/CountryDto.java b/src/main/java/net/contargo/iris/countries/dto/CountryDto.java new file mode 100644 index 00000000..b171c51f --- /dev/null +++ b/src/main/java/net/contargo/iris/countries/dto/CountryDto.java @@ -0,0 +1,64 @@ +package net.contargo.iris.countries.dto; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + + +/** + * View Bean for exposing countries over the API. + * + * @author Arnold Franke - franke@synyx.de + */ +public class CountryDto { + + private String name; + private String value; + + public CountryDto(String name, String value) { + + this.name = name; + this.value = value; + } + + + public CountryDto() { + + // Needed for Jackson Mapping + } + + public String getName() { + + return name; + } + + + public String getValue() { + + return value; + } + + + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (!(obj instanceof CountryDto)) { + return false; + } + + CountryDto other = (CountryDto) obj; + + return new EqualsBuilder().append(this.hashCode(), other.hashCode()).append(this.name, other.name).append( + this.value, other.value).isEquals(); + } + + + @Override + public int hashCode() { + + return new HashCodeBuilder().append(this.name).append(this.value).toHashCode(); + } +} diff --git a/src/main/java/net/contargo/iris/countries/dto/CountryDtoService.java b/src/main/java/net/contargo/iris/countries/dto/CountryDtoService.java new file mode 100644 index 00000000..96786273 --- /dev/null +++ b/src/main/java/net/contargo/iris/countries/dto/CountryDtoService.java @@ -0,0 +1,14 @@ +package net.contargo.iris.countries.dto; + +import java.util.List; + + +/** + * @author Arnold Franke - franke@synyx.de + * + *

Created: Date: 8/23/13 Time: 8:55 AM

+ */ +public interface CountryDtoService { + + List getCountries(); +} diff --git a/src/main/java/net/contargo/iris/countries/service/CountryCode.java b/src/main/java/net/contargo/iris/countries/service/CountryCode.java new file mode 100644 index 00000000..1bb33877 --- /dev/null +++ b/src/main/java/net/contargo/iris/countries/service/CountryCode.java @@ -0,0 +1,43 @@ +package net.contargo.iris.countries.service; + +/** + * Enum representing country codes (ISO 3166-1 alpha2 code), e.g. de for Germany. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +enum CountryCode { + + OPTIONAL("Country (optional)", ""), + GERMANY("Germany", "DE"), + NETHERLANDS("Netherlands", "NL"), + BELGIUM("Belgium", "BE"), + LUXEMBOURG("Luxembourg", "LU"), + FRANCE("France", "FR"), + SWITZERLAND("Switzerland", "CH"), + LIECHTENSTEIN("Liechtenstein", "LI"), + AUSTRIA("Austria", "AT"), + CZECH_REPUBLIC("Czech Republic", "CZ"), + POLAND("Poland", "PL"), + DENMARK("Denmark", "DK"); + + private String name; + private String value; + + private CountryCode(String name, String value) { + + this.name = name; + this.value = value; + } + + public String getValue() { + + return value; + } + + + public String getName() { + + return name; + } +} diff --git a/src/main/java/net/contargo/iris/countries/service/CountryCodeCountryService.java b/src/main/java/net/contargo/iris/countries/service/CountryCodeCountryService.java new file mode 100644 index 00000000..85cc8f52 --- /dev/null +++ b/src/main/java/net/contargo/iris/countries/service/CountryCodeCountryService.java @@ -0,0 +1,23 @@ +package net.contargo.iris.countries.service; + +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public class CountryCodeCountryService implements CountryService { + + @Override + public Map getCountries() { + + Map countryMap = new LinkedHashMap<>(); + + for (CountryCode countryCode : CountryCode.values()) { + countryMap.put(countryCode.getName(), countryCode.getValue()); + } + + return countryMap; + } +} diff --git a/src/main/java/net/contargo/iris/countries/service/CountryService.java b/src/main/java/net/contargo/iris/countries/service/CountryService.java new file mode 100644 index 00000000..d95dd95d --- /dev/null +++ b/src/main/java/net/contargo/iris/countries/service/CountryService.java @@ -0,0 +1,12 @@ +package net.contargo.iris.countries.service; + +import java.util.Map; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public interface CountryService { + + Map getCountries(); +} diff --git a/src/main/java/net/contargo/iris/distance/service/ConnectionDistanceService.java b/src/main/java/net/contargo/iris/distance/service/ConnectionDistanceService.java new file mode 100644 index 00000000..f889ad81 --- /dev/null +++ b/src/main/java/net/contargo/iris/distance/service/ConnectionDistanceService.java @@ -0,0 +1,41 @@ +package net.contargo.iris.distance.service; + +import net.contargo.iris.connection.MainRunConnection; + +import java.math.BigDecimal; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +public interface ConnectionDistanceService { + + /** + * Extracts the distance of a {@link net.contargo.iris.connection.MainRunConnection}. + * + * @param mainrunConnection + * + * @return BigDecimal + */ + BigDecimal getDistance(MainRunConnection mainrunConnection); + + + /** + * Extracts the dieselDistance of a {@link net.contargo.iris.connection.MainRunConnection}. + * + * @param mainrunConnection + * + * @return BigDecimal + */ + BigDecimal getDieselDistance(MainRunConnection mainrunConnection); + + + /** + * Extracts the electricDistance of a {@link net.contargo.iris.connection.MainRunConnection}. + * + * @param mainrunConnection + * + * @return BigDecimal + */ + BigDecimal getElectricDistance(MainRunConnection mainrunConnection); +} diff --git a/src/main/java/net/contargo/iris/distance/service/ConnectionDistanceServiceImpl.java b/src/main/java/net/contargo/iris/distance/service/ConnectionDistanceServiceImpl.java new file mode 100644 index 00000000..275463f6 --- /dev/null +++ b/src/main/java/net/contargo/iris/distance/service/ConnectionDistanceServiceImpl.java @@ -0,0 +1,51 @@ +package net.contargo.iris.distance.service; + +import net.contargo.iris.connection.MainRunConnection; +import net.contargo.iris.rounding.RoundingService; + +import java.math.BigDecimal; + + +/** + * Default implementation of {@link ConnectionDistanceService}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public class ConnectionDistanceServiceImpl implements ConnectionDistanceService { + + private final RoundingService roundingService; + + public ConnectionDistanceServiceImpl(RoundingService roundingService) { + + this.roundingService = roundingService; + } + + /** + * @see ConnectionDistanceService#getDistance(net.contargo.iris.connection.MainRunConnection) + */ + @Override + public BigDecimal getDistance(MainRunConnection mainrunConnection) { + + return roundingService.roundDistance(mainrunConnection.getTotalDistance()); + } + + + /** + * @see ConnectionDistanceService#getDieselDistance(net.contargo.iris.connection.MainRunConnection) + */ + @Override + public BigDecimal getDieselDistance(MainRunConnection mainrunConnection) { + + return roundingService.roundDistance(mainrunConnection.getDieselDistance()); + } + + + /** + * @see ConnectionDistanceService#getElectricDistance(net.contargo.iris.connection.MainRunConnection) + */ + @Override + public BigDecimal getElectricDistance(MainRunConnection mainrunConnection) { + + return roundingService.roundDistance(mainrunConnection.getElectricDistance()); + } +} diff --git a/src/main/java/net/contargo/iris/distance/service/DistanceService.java b/src/main/java/net/contargo/iris/distance/service/DistanceService.java new file mode 100644 index 00000000..89ed611f --- /dev/null +++ b/src/main/java/net/contargo/iris/distance/service/DistanceService.java @@ -0,0 +1,33 @@ +package net.contargo.iris.distance.service; + +import net.contargo.iris.truck.TruckRoute; + +import java.math.BigDecimal; + + +/** + * Extracts specific properties and calculates the correct value with or without rounding. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public interface DistanceService { + + /** + * Extracts the distance of a {@link TruckRoute}. + * + * @param truckRoute to get distance of + * + * @return BigDecimal + */ + BigDecimal getDistance(TruckRoute truckRoute); + + + /** + * Extracts the toll distance of a {@link TruckRoute}. + * + * @param truckRoute to get toll of + * + * @return BigDecimal + */ + BigDecimal getTollDistance(TruckRoute truckRoute); +} diff --git a/src/main/java/net/contargo/iris/distance/service/DistanceServiceImpl.java b/src/main/java/net/contargo/iris/distance/service/DistanceServiceImpl.java new file mode 100644 index 00000000..cda41d5b --- /dev/null +++ b/src/main/java/net/contargo/iris/distance/service/DistanceServiceImpl.java @@ -0,0 +1,35 @@ +package net.contargo.iris.distance.service; + +import net.contargo.iris.rounding.RoundingService; +import net.contargo.iris.truck.TruckRoute; + +import java.math.BigDecimal; + + +/** + * Handles the extraction and calculation of the distances. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public class DistanceServiceImpl implements DistanceService { + + private final RoundingService roundingService; + + public DistanceServiceImpl(RoundingService roundingService) { + + this.roundingService = roundingService; + } + + @Override + public BigDecimal getDistance(TruckRoute truckRoute) { + + return roundingService.roundDistance(truckRoute.getDistance()); + } + + + @Override + public BigDecimal getTollDistance(TruckRoute truckRoute) { + + return roundingService.roundDistance(truckRoute.getTollDistance()); + } +} diff --git a/src/main/java/net/contargo/iris/distancecloud/DistanceCloudAddress.java b/src/main/java/net/contargo/iris/distancecloud/DistanceCloudAddress.java new file mode 100644 index 00000000..ca1a42be --- /dev/null +++ b/src/main/java/net/contargo/iris/distancecloud/DistanceCloudAddress.java @@ -0,0 +1,156 @@ +package net.contargo.iris.distancecloud; + +import net.contargo.iris.address.staticsearch.StaticAddress; + +import java.math.BigDecimal; +import java.math.BigInteger; + + +/** + * Container for addresses, which are used to describe Addresses within a cloud around a certain GeoLocation. + * + * @author Michael Herbold - herbold@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public class DistanceCloudAddress { + + private String city; + private String postalcode; + private String suburb; + private BigDecimal distance; + private BigDecimal tollDistance; + private BigDecimal airLineDistanceMeter; + private String country; + private String errorMessage = ""; + private String hashKey; + private BigInteger uniqueId; + + public DistanceCloudAddress(StaticAddress staticAddress) { + + this.city = staticAddress.getCity(); + this.suburb = staticAddress.getSuburb(); + this.postalcode = staticAddress.getPostalcode(); + this.country = staticAddress.getCountry(); + this.hashKey = staticAddress.getHashKey(); + this.uniqueId = staticAddress.getUniqueId(); + } + + public String getErrorMessage() { + + return errorMessage; + } + + + public void setErrorMessage(String errorMessage) { + + this.errorMessage = errorMessage; + } + + + public String getCity() { + + return city; + } + + + public void setCity(String city) { + + this.city = city; + } + + + public String getPostalcode() { + + return postalcode; + } + + + public void setPostalcode(String postalcode) { + + this.postalcode = postalcode; + } + + + public String getSuburb() { + + return suburb; + } + + + public void setSuburb(String suburb) { + + this.suburb = suburb; + } + + + public BigDecimal getDistance() { + + return distance; + } + + + public void setDistance(BigDecimal distance) { + + this.distance = distance; + } + + + public BigDecimal getTollDistance() { + + return tollDistance; + } + + + public void setTollDistance(BigDecimal tollDistance) { + + this.tollDistance = tollDistance; + } + + + public BigDecimal getAirLineDistanceMeter() { + + return airLineDistanceMeter; + } + + + public void setAirLineDistanceMeter(BigDecimal airLineDistanceMeter) { + + this.airLineDistanceMeter = airLineDistanceMeter; + } + + + public String getCountry() { + + return country; + } + + + public void setCountry(String country) { + + this.country = country; + } + + + public String getHashKey() { + + return hashKey; + } + + + public void setHashKey(String hashKey) { + + this.hashKey = hashKey; + } + + + public BigInteger getUniqueId() { + + return uniqueId; + } + + + public void setUniqueId(BigInteger uniqueId) { + + this.uniqueId = uniqueId; + } +} diff --git a/src/main/java/net/contargo/iris/distancecloud/api/DistanceCloudAddressResponse.java b/src/main/java/net/contargo/iris/distancecloud/api/DistanceCloudAddressResponse.java new file mode 100644 index 00000000..96115a99 --- /dev/null +++ b/src/main/java/net/contargo/iris/distancecloud/api/DistanceCloudAddressResponse.java @@ -0,0 +1,30 @@ +package net.contargo.iris.distancecloud.api; + +import net.contargo.iris.distancecloud.dto.DistanceCloudAddressDto; + +import org.springframework.hateoas.ResourceSupport; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +public class DistanceCloudAddressResponse extends ResourceSupport { + + private DistanceCloudAddressDto address; + + public DistanceCloudAddressResponse(DistanceCloudAddressDto address) { + + this.address = address; + } + + public DistanceCloudAddressDto getAddress() { + + return address; + } + + + public void setAddress(DistanceCloudAddressDto address) { + + this.address = address; + } +} diff --git a/src/main/java/net/contargo/iris/distancecloud/api/DistanceCloudApiController.java b/src/main/java/net/contargo/iris/distancecloud/api/DistanceCloudApiController.java new file mode 100644 index 00000000..73637637 --- /dev/null +++ b/src/main/java/net/contargo/iris/distancecloud/api/DistanceCloudApiController.java @@ -0,0 +1,60 @@ +package net.contargo.iris.distancecloud.api; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; + +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.distancecloud.dto.DistanceCloudAddressDto; +import net.contargo.iris.distancecloud.dto.DistanceCloudAddressDtoService; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.math.BigInteger; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + + +/** + * @author Arnold Franke - franke@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +@Api(value = "", description = "API for calculating cloud-distances.") +@Controller +public class DistanceCloudApiController extends AbstractController { + + private final DistanceCloudAddressDtoService distanceCloudAddressDtoService; + + @Autowired + public DistanceCloudApiController(DistanceCloudAddressDtoService distanceCloudAddressDtoService) { + + this.distanceCloudAddressDtoService = distanceCloudAddressDtoService; + } + + @ApiOperation( + notes = "Calculates the cloud-distance between the given terminal and static address.", + value = "Calculates the cloud-distance between the given terminal and static address." + ) + @RequestMapping(value = "distancecloudaddress", method = RequestMethod.GET) + @ResponseBody + public DistanceCloudAddressResponse cloudAddress(@RequestParam("terminal") BigInteger terminalUid, + @RequestParam("address") BigInteger staticAddressUid) { + + DistanceCloudAddressDto address = distanceCloudAddressDtoService.getAddressInCloud(terminalUid, + staticAddressUid); + + DistanceCloudAddressResponse distanceCloudAddressResponse = new DistanceCloudAddressResponse(address); + + distanceCloudAddressResponse.add(linkTo(methodOn(getClass()).cloudAddress(terminalUid, staticAddressUid)) + .withSelfRel()); + + return distanceCloudAddressResponse; + } +} diff --git a/src/main/java/net/contargo/iris/distancecloud/dto/DistanceCloudAddressDto.java b/src/main/java/net/contargo/iris/distancecloud/dto/DistanceCloudAddressDto.java new file mode 100644 index 00000000..887e6bf2 --- /dev/null +++ b/src/main/java/net/contargo/iris/distancecloud/dto/DistanceCloudAddressDto.java @@ -0,0 +1,99 @@ +package net.contargo.iris.distancecloud.dto; + +import net.contargo.iris.distancecloud.DistanceCloudAddress; + +import java.math.BigDecimal; + + +/** + * Container for addresses, which are used to describe Addresses within a cloud around a certain GeoLocation. + * + * @author Michael Herbold - herbold@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public final class DistanceCloudAddressDto { + + private final String city; + private final String postalcode; + private final String suburb; + private final BigDecimal distance; + private final BigDecimal tollDistance; + private final BigDecimal airLineDistanceMeter; + private final String country; + private final String errorMessage; + private final String hashKey; + private final String uniqueId; + + public DistanceCloudAddressDto(DistanceCloudAddress address) { + + this.city = address.getCity(); + this.suburb = address.getSuburb(); + this.postalcode = address.getPostalcode(); + this.country = address.getCountry(); + this.hashKey = address.getHashKey(); + this.distance = address.getDistance(); + this.tollDistance = address.getTollDistance(); + this.airLineDistanceMeter = address.getAirLineDistanceMeter(); + this.errorMessage = address.getErrorMessage(); + this.uniqueId = String.valueOf(address.getUniqueId()); + } + + public String getErrorMessage() { + + return errorMessage; + } + + + public String getCity() { + + return city; + } + + + public String getPostalcode() { + + return postalcode; + } + + + public String getSuburb() { + + return suburb; + } + + + public BigDecimal getDistance() { + + return distance; + } + + + public BigDecimal getTollDistance() { + + return tollDistance; + } + + + public BigDecimal getAirLineDistanceMeter() { + + return airLineDistanceMeter; + } + + + public String getCountry() { + + return country; + } + + + public String getHashKey() { + + return hashKey; + } + + + public String getUniqueId() { + + return uniqueId; + } +} diff --git a/src/main/java/net/contargo/iris/distancecloud/dto/DistanceCloudAddressDtoService.java b/src/main/java/net/contargo/iris/distancecloud/dto/DistanceCloudAddressDtoService.java new file mode 100644 index 00000000..636e5820 --- /dev/null +++ b/src/main/java/net/contargo/iris/distancecloud/dto/DistanceCloudAddressDtoService.java @@ -0,0 +1,13 @@ +package net.contargo.iris.distancecloud.dto; + +import java.math.BigInteger; + + +/** + * @author Arnold Franke - franke@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public interface DistanceCloudAddressDtoService { + + DistanceCloudAddressDto getAddressInCloud(BigInteger terminalUid, BigInteger staticAddressUid); +} diff --git a/src/main/java/net/contargo/iris/distancecloud/dto/DistanceCloudAddressDtoServiceImpl.java b/src/main/java/net/contargo/iris/distancecloud/dto/DistanceCloudAddressDtoServiceImpl.java new file mode 100644 index 00000000..54e911ea --- /dev/null +++ b/src/main/java/net/contargo/iris/distancecloud/dto/DistanceCloudAddressDtoServiceImpl.java @@ -0,0 +1,29 @@ +package net.contargo.iris.distancecloud.dto; + +import net.contargo.iris.distancecloud.DistanceCloudAddress; +import net.contargo.iris.distancecloud.service.DistanceCloudAddressService; + +import java.math.BigInteger; + + +/** + * @author Arnold Franke - franke@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public class DistanceCloudAddressDtoServiceImpl implements DistanceCloudAddressDtoService { + + private final DistanceCloudAddressService service; + + public DistanceCloudAddressDtoServiceImpl(DistanceCloudAddressService service) { + + this.service = service; + } + + @Override + public DistanceCloudAddressDto getAddressInCloud(BigInteger terminalUid, BigInteger staticAddressUid) { + + DistanceCloudAddress address = service.getAddressInCloud(terminalUid, staticAddressUid); + + return new DistanceCloudAddressDto(address); + } +} diff --git a/src/main/java/net/contargo/iris/distancecloud/service/DistanceCloudAddressService.java b/src/main/java/net/contargo/iris/distancecloud/service/DistanceCloudAddressService.java new file mode 100644 index 00000000..aa952122 --- /dev/null +++ b/src/main/java/net/contargo/iris/distancecloud/service/DistanceCloudAddressService.java @@ -0,0 +1,15 @@ +package net.contargo.iris.distancecloud.service; + +import net.contargo.iris.distancecloud.DistanceCloudAddress; + +import java.math.BigInteger; + + +/** + * @author Arnold Franke - franke@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public interface DistanceCloudAddressService { + + DistanceCloudAddress getAddressInCloud(BigInteger terminalUid, BigInteger staticAddressUid); +} diff --git a/src/main/java/net/contargo/iris/distancecloud/service/DistanceCloudAddressServiceImpl.java b/src/main/java/net/contargo/iris/distancecloud/service/DistanceCloudAddressServiceImpl.java new file mode 100644 index 00000000..55274643 --- /dev/null +++ b/src/main/java/net/contargo/iris/distancecloud/service/DistanceCloudAddressServiceImpl.java @@ -0,0 +1,94 @@ +package net.contargo.iris.distancecloud.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.staticsearch.StaticAddress; +import net.contargo.iris.address.staticsearch.persistence.StaticAddressRepository; +import net.contargo.iris.distancecloud.DistanceCloudAddress; +import net.contargo.iris.gis.service.GisService; +import net.contargo.iris.rounding.RoundingService; +import net.contargo.iris.terminal.Terminal; +import net.contargo.iris.terminal.service.TerminalService; +import net.contargo.iris.truck.TruckRoute; +import net.contargo.iris.truck.service.OSRMNonRoutableRouteException; +import net.contargo.iris.truck.service.TruckRouteService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.invoke.MethodHandles; + +import java.math.BigDecimal; +import java.math.BigInteger; + + +/** + * @author Marc Kannegiesser - kannegiesser@synyx.de + * @author Tobias Schneider - schneider@synyx.de + * @author Oliver Messner - messner@synyx.de + * @author Sandra Thieme - thieme@synyx.de + */ +public class DistanceCloudAddressServiceImpl implements DistanceCloudAddressService { + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + private static final BigDecimal MULTIPLIER = new BigDecimal(2); + + private final RoundingService roundingService; + + private final TruckRouteService truckRouteService; + private final StaticAddressRepository repository; + private final GisService gisService; + private final TerminalService terminalService; + + public DistanceCloudAddressServiceImpl(TruckRouteService truckRouteService, StaticAddressRepository repository, + RoundingService roundingService, GisService gisService, TerminalService terminalService) { + + this.truckRouteService = truckRouteService; + this.repository = repository; + this.roundingService = roundingService; + this.gisService = gisService; + this.terminalService = terminalService; + } + + @Override + public DistanceCloudAddress getAddressInCloud(BigInteger terminalUid, BigInteger staticAddressUid) { + + Terminal terminal = terminalService.getByUniqueId(terminalUid); + StaticAddress staticAddress = repository.findByUniqueId(staticAddressUid); + + try { + return createDistanceCloudAddress(terminal, staticAddress); + } catch (OSRMNonRoutableRouteException e) { + LOG.error("Cold not determine route, adding information to distance cloud address bean", e); + + DistanceCloudAddress distanceCloudAddress = new DistanceCloudAddress(staticAddress); + distanceCloudAddress.setErrorMessage("Routing not possible"); + + return distanceCloudAddress; + } + } + + + public DistanceCloudAddress createDistanceCloudAddress(GeoLocation geoLocation, StaticAddress staticAddress) { + + LOG.debug("Creating distance-cloud-address item for {} and {}", geoLocation, staticAddress); + + TruckRoute terminalToAddressRoute = truckRouteService.route(geoLocation, staticAddress); + DistanceCloudAddress cloudAddress = new DistanceCloudAddress(staticAddress); + + // distance from geoLocation -> address + BigDecimal distanceRounded = terminalToAddressRoute.getDistance(); + distanceRounded = roundingService.roundDistance(distanceRounded); + cloudAddress.setDistance(distanceRounded); + + // toll distance from geoLocation -> address + BigDecimal tollDistanceRounded = terminalToAddressRoute.getTollDistance(); + tollDistanceRounded = roundingService.roundDistance(tollDistanceRounded); + cloudAddress.setTollDistance(tollDistanceRounded.multiply(MULTIPLIER)); + + // air-line distance + BigDecimal airLineDistanceInMeters = gisService.calcAirLineDistInMeters(geoLocation, staticAddress); + cloudAddress.setAirLineDistanceMeter(roundingService.roundDistance(airLineDistanceInMeters)); + + return cloudAddress; + } +} diff --git a/src/main/java/net/contargo/iris/duration/service/DurationService.java b/src/main/java/net/contargo/iris/duration/service/DurationService.java new file mode 100644 index 00000000..789b41ce --- /dev/null +++ b/src/main/java/net/contargo/iris/duration/service/DurationService.java @@ -0,0 +1,23 @@ +package net.contargo.iris.duration.service; + +import net.contargo.iris.truck.TruckRoute; + +import java.math.BigDecimal; + + +/** + * Interface for implementation of duration services.. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public interface DurationService { + + /** + * Returns the duration. + * + * @param truckRoute to extract the duration + * + * @return the rounded duration of a {@link TruckRoute} + */ + BigDecimal getDuration(TruckRoute truckRoute); +} diff --git a/src/main/java/net/contargo/iris/duration/service/DurationServiceImpl.java b/src/main/java/net/contargo/iris/duration/service/DurationServiceImpl.java new file mode 100644 index 00000000..2eb98f38 --- /dev/null +++ b/src/main/java/net/contargo/iris/duration/service/DurationServiceImpl.java @@ -0,0 +1,28 @@ +package net.contargo.iris.duration.service; + +import net.contargo.iris.rounding.RoundingService; +import net.contargo.iris.truck.TruckRoute; + +import java.math.BigDecimal; + + +/** + * Service implementation of {@link DurationService} to receive (rounded) duration information from entities. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public class DurationServiceImpl implements DurationService { + + private final RoundingService roundingService; + + public DurationServiceImpl(RoundingService roundingService) { + + this.roundingService = roundingService; + } + + @Override + public BigDecimal getDuration(TruckRoute truckRoute) { + + return roundingService.roundDuration(truckRoute.getDuration()); + } +} diff --git a/src/main/java/net/contargo/iris/enricher/api/RouteEnricherApiController.java b/src/main/java/net/contargo/iris/enricher/api/RouteEnricherApiController.java new file mode 100644 index 00000000..6dda79e6 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/api/RouteEnricherApiController.java @@ -0,0 +1,97 @@ +package net.contargo.iris.enricher.api; + +import com.mangofactory.swagger.annotations.ApiIgnore; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; + +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.connection.dto.RouteDto; +import net.contargo.iris.enricher.dto.EnricherDtoService; +import net.contargo.iris.terminal.dto.TerminalDto; +import net.contargo.iris.terminal.dto.TerminalDtoService; + +import org.slf4j.Logger; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.ui.Model; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.lang.invoke.MethodHandles; + +import java.math.BigInteger; + +import static org.slf4j.LoggerFactory.getLogger; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + + +/** + * @author Arnold Franke - franke@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@Api( + value = AbstractController.SLASH + RouteEnricherApiController.ROUTE_DETAILS_URL, + description = "Api to get eiriched routes" +) +@Controller +@RequestMapping(AbstractController.SLASH + RouteEnricherApiController.ROUTE_DETAILS_URL) +public class RouteEnricherApiController extends AbstractController { + + public static final String ROUTE_DETAILS_URL = ROUTE_DETAILS; + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private final EnricherDtoService enricherDtoService; + private final TerminalDtoService terminalDtoService; + + @Autowired + RouteEnricherApiController(EnricherDtoService enricherDtoService, TerminalDtoService terminalDtoService) { + + this.enricherDtoService = enricherDtoService; + this.terminalDtoService = terminalDtoService; + } + + @ApiOperation( + value = "Takes a route and returns it enriched with additional information.", + notes = "Takes a route and returns it enriched with additional information. The route consists of an arbitrary " + + "number of route parts which are passed as request params.\n\n" + + "Example usage:\n\n" + + "/api/routedetails?data.parts[0].origin.latitude=51.39119&data.parts[0].origin.longitude=6.72873&\n" + + "data.parts[0].destination.latitude=51.36833&data.parts[0].destination.longitude=4.3&\n" + + "data.parts[0].containerType=TWENTY_LIGHT&data.parts[0].containerState=FULL&\n" + + "data.parts[0].routeType=TRUCK&data.parts[1].origin.latitude=51.36833&\n" + + "data.parts[1].origin.longitude=4.3&data.parts[1].destination.latitude=51.39119&\n" + + "data.parts[1].destination.longitude=6.72873&data.parts[1].containerType=TWENTY_LIGHT&\n" + + "data.parts[1].containerState=FULL&data.parts[1].routeType=TRUCK" + ) + @RequestMapping(method = RequestMethod.GET) + @ModelAttribute(RESPONSE) + public RouteResponse getEnrichedRoute(@ApiIgnore RouteDto route, + @RequestParam(value = "terminal", required = false) + @ApiIgnore String terminalUid, Model model) { + + model.asMap().remove("routeDto"); + + if (terminalUid != null) { + TerminalDto terminal = terminalDtoService.getByUid(new BigInteger(terminalUid)); + route.setResponsibleTerminal(terminal); + } + + RouteResponse response = new RouteResponse(); + response.setRoute(enricherDtoService.enrich(route)); + response.add(linkTo(getClass()).withSelfRel()); + + LOG.info("API: Responding routedetails with a route with {} parts. Route name is {}", + response.getRoute().size(), response.getRoute().getName()); + + return response; + } +} diff --git a/src/main/java/net/contargo/iris/enricher/api/RouteResponse.java b/src/main/java/net/contargo/iris/enricher/api/RouteResponse.java new file mode 100644 index 00000000..a0654bf0 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/api/RouteResponse.java @@ -0,0 +1,26 @@ +package net.contargo.iris.enricher.api; + +import net.contargo.iris.connection.dto.RouteDto; + +import org.springframework.hateoas.ResourceSupport; + + +/** + * @author Arnold Franke - franke@synyx.de + * @author Tobias Schneid0r - schneider@synyx.de + */ +class RouteResponse extends ResourceSupport { + + private RouteDto route; + + public RouteDto getRoute() { + + return route; + } + + + public void setRoute(RouteDto route) { + + this.route = route; + } +} diff --git a/src/main/java/net/contargo/iris/enricher/dto/EnricherDtoService.java b/src/main/java/net/contargo/iris/enricher/dto/EnricherDtoService.java new file mode 100644 index 00000000..8918319f --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/dto/EnricherDtoService.java @@ -0,0 +1,19 @@ +package net.contargo.iris.enricher.dto; + +import net.contargo.iris.connection.dto.RouteDto; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public interface EnricherDtoService { + + /** + * Enriches a {@link net.contargo.iris.connection.dto.RouteDto}. + * + * @param route to be enriched + * + * @return enriched {@link net.contargo.iris.connection.dto.RouteDto} + */ + RouteDto enrich(RouteDto route); +} diff --git a/src/main/java/net/contargo/iris/enricher/dto/EnricherDtoServiceImpl.java b/src/main/java/net/contargo/iris/enricher/dto/EnricherDtoServiceImpl.java new file mode 100644 index 00000000..007bbb2c --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/dto/EnricherDtoServiceImpl.java @@ -0,0 +1,24 @@ +package net.contargo.iris.enricher.dto; + +import net.contargo.iris.connection.dto.RouteDto; +import net.contargo.iris.enricher.service.EnricherService; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public class EnricherDtoServiceImpl implements EnricherDtoService { + + private final EnricherService enricherService; + + public EnricherDtoServiceImpl(EnricherService enricherService) { + + this.enricherService = enricherService; + } + + @Override + public RouteDto enrich(RouteDto routeDto) { + + return new RouteDto(enricherService.enrich(routeDto.toRoute())); + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/AirLineDistancePartEnricher.java b/src/main/java/net/contargo/iris/enricher/service/AirLineDistancePartEnricher.java new file mode 100644 index 00000000..9adfb760 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/AirLineDistancePartEnricher.java @@ -0,0 +1,39 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.gis.service.GisService; +import net.contargo.iris.rounding.RoundingService; +import net.contargo.iris.route.RoutePart; + +import java.math.BigDecimal; + + +/** + * RoutePartEnricher for Air-Line-Distance. + * + * @author Tobias Schneider - schneider@synyx.de + */ +class AirLineDistancePartEnricher implements RoutePartEnricher { + + private static final BigDecimal METERS_IN_A_KILOMETER = new BigDecimal(1000); + + private final GisService gisService; + + private final RoundingService roundingService; + + AirLineDistancePartEnricher(GisService gisService, RoundingService roundingService) { + + this.gisService = gisService; + this.roundingService = roundingService; + } + + @Override + public void enrich(RoutePart routePart, EnricherContext context) { + + BigDecimal airLineDistance = gisService.calcAirLineDistInMeters(routePart.getOrigin(), + routePart.getDestination()); + airLineDistance = airLineDistance.divide(METERS_IN_A_KILOMETER); + airLineDistance = roundingService.roundDistance(airLineDistance); + + routePart.getData().setAirLineDistance(airLineDistance); + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/Co2PartEnricher.java b/src/main/java/net/contargo/iris/enricher/service/Co2PartEnricher.java new file mode 100644 index 00000000..36330098 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/Co2PartEnricher.java @@ -0,0 +1,45 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.co2.advice.Co2PartStrategy; +import net.contargo.iris.co2.advice.Co2PartStrategyAdvisor; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RouteType; + +import org.springframework.beans.factory.annotation.Autowired; + +import java.math.BigDecimal; + + +/** + * @author Aljona Murygina - murygina@synyx.de + * @author Vincent Potucek - potucek@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +class Co2PartEnricher implements RoutePartEnricher { + + private final Co2PartStrategyAdvisor co2PartStrategyAdvisor; + + @Autowired + Co2PartEnricher(Co2PartStrategyAdvisor co2PartStrategyAdvisor) { + + this.co2PartStrategyAdvisor = co2PartStrategyAdvisor; + } + + @Override + public void enrich(RoutePart routePart, EnricherContext context) throws CriticalEnricherException { + + RouteType routeType = routePart.getRouteType(); + + Co2PartStrategy strategy; + + try { + strategy = co2PartStrategyAdvisor.advice(routeType); + } catch (IllegalStateException e) { + throw new CriticalEnricherException("Co2 part enrichment failed", e); + } + + BigDecimal emission = strategy.getEmissionForRoutePart(routePart); + + routePart.getData().setCo2(emission); + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/Co2TotalEnricher.java b/src/main/java/net/contargo/iris/enricher/service/Co2TotalEnricher.java new file mode 100644 index 00000000..06d715bc --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/Co2TotalEnricher.java @@ -0,0 +1,43 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.co2.service.Co2Service; +import net.contargo.iris.route.Route; + +import org.springframework.beans.factory.annotation.Autowired; + +import java.math.BigDecimal; + + +/** + * Enricher to set emission information on {@link Route}s. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +class Co2TotalEnricher implements RouteTotalEnricher { + + private final Co2Service co2Service; + + @Autowired + Co2TotalEnricher(Co2Service co2Service) { + + this.co2Service = co2Service; + } + + @Override + public void enrich(Route route, EnricherContext context) throws CriticalEnricherException { + + BigDecimal co2; + BigDecimal co2DirectTruck; + + try { + co2 = co2Service.getEmission(route); + co2DirectTruck = co2Service.getEmissionDirectTruck(route); + } catch (IllegalStateException e) { + throw new CriticalEnricherException("Failed calculating co2 emission", e); + } + + route.getData().setCo2(co2); + route.getData().setCo2DirectTruck(co2DirectTruck); + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/CriticalEnricherException.java b/src/main/java/net/contargo/iris/enricher/service/CriticalEnricherException.java new file mode 100644 index 00000000..258b0fad --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/CriticalEnricherException.java @@ -0,0 +1,12 @@ +package net.contargo.iris.enricher.service; + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +public class CriticalEnricherException extends Exception { + + public CriticalEnricherException(String message, Throwable t) { + + super(message, t); + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/Enricher.java b/src/main/java/net/contargo/iris/enricher/service/Enricher.java new file mode 100644 index 00000000..101dd3f7 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/Enricher.java @@ -0,0 +1,9 @@ +package net.contargo.iris.enricher.service; + +/** + * Common interface for all enrichers. + * + * @author Tobias Schneider - schneider@synyx.de + */ +interface Enricher { +} diff --git a/src/main/java/net/contargo/iris/enricher/service/EnricherContext.java b/src/main/java/net/contargo/iris/enricher/service/EnricherContext.java new file mode 100644 index 00000000..9b14dda1 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/EnricherContext.java @@ -0,0 +1,78 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.route.RouteDirection; +import net.contargo.iris.route.RouteType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * @author Sandra Thieme - thieme@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +// NOSONAR trailing comment... +final class EnricherContext { // NOSONAR class can not be instantiated because correct use of builder pattern + + private final RouteDirection routeDirection; + private final List routeTypes; + private final Map errors; + + private EnricherContext(Builder builder) { + + this.routeDirection = builder.routeDirection; + this.routeTypes = builder.routeTypes; + this.errors = new HashMap<>(); + } + + public RouteDirection getRouteDirection() { + + return routeDirection; + } + + + public List getRouteTypes() { + + return routeTypes; + } + + + public Map getErrors() { + + return errors; + } + + + public void addError(String type, String errorMessage) { + + errors.put(type, errorMessage); + } + + public static class Builder { + + private RouteDirection routeDirection; + private List routeTypes; + + public Builder routeDirection(RouteDirection value) { + + routeDirection = value; + + return this; + } + + + public Builder routeTypes(List value) { + + routeTypes = value; + + return this; + } + + + public EnricherContext build() { + + return new EnricherContext(this); + } + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/EnricherService.java b/src/main/java/net/contargo/iris/enricher/service/EnricherService.java new file mode 100644 index 00000000..331e7d2b --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/EnricherService.java @@ -0,0 +1,19 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.route.Route; + + +/** + * @author Tobias Schneider - schneider@synyx.de + */ +public interface EnricherService { + + /** + * Enriches a {@link Route}. + * + * @param route to be enriched + * + * @return enriched {@link Route} + */ + Route enrich(Route route); +} diff --git a/src/main/java/net/contargo/iris/enricher/service/EnricherServiceImpl.java b/src/main/java/net/contargo/iris/enricher/service/EnricherServiceImpl.java new file mode 100644 index 00000000..23ca8695 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/EnricherServiceImpl.java @@ -0,0 +1,105 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteData; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RoutePartData; + +import org.slf4j.Logger; + +import org.springframework.util.Assert; + +import java.lang.invoke.MethodHandles; + +import java.util.List; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Implementation of interface {@link EnricherService}. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public class EnricherServiceImpl implements EnricherService { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private final List routePartEnricherList; + private final List routeTotalEnricherList; + + public EnricherServiceImpl(List routePartEnricherList, + List routeTotalEnricherList) { + + Assert.notNull(routePartEnricherList); + Assert.notNull(routeTotalEnricherList); + + this.routePartEnricherList = routePartEnricherList; + this.routeTotalEnricherList = routeTotalEnricherList; + } + + @Override + public Route enrich(Route route) { + + EnricherContext enricherContext = new EnricherContext.Builder().routeDirection(route.getDirection()).build(); + + try { + for (RoutePart routePart : route.getData().getParts()) { + enrich(routePart, enricherContext, routePartEnricherList); + } + + enrich(route, enricherContext, routeTotalEnricherList); + } catch (CriticalEnricherException e) { + handleError(route, enricherContext); + + return route; + } finally { + route.setErrors(enricherContext.getErrors()); + } + + return route; + } + + + private void enrich(RoutePart routePart, EnricherContext context, List routePartEnrichers) + throws CriticalEnricherException { + + for (RoutePartEnricher routePartEnricher : routePartEnrichers) { + if (LOG.isDebugEnabled()) { + LOG.debug("Enriching route using RoutePartEnricher {}", routePartEnricher.getClass().getSimpleName()); + } + + routePartEnricher.enrich(routePart, context); + } + } + + + private void enrich(Route route, EnricherContext context, List routeTotalEnrichers) + throws CriticalEnricherException { + + for (RouteTotalEnricher routeTotalEnricher : routeTotalEnrichers) { + if (LOG.isDebugEnabled()) { + LOG.debug("Enriching route using RouteTotalEnricher {}", routeTotalEnricher.getClass().getSimpleName()); + } + + routeTotalEnricher.enrich(route, context); + } + } + + + private void handleError(Route route, EnricherContext context) { + + List parts = route.getData().getParts(); + + for (RoutePart part : parts) { + part.setData(new RoutePartData()); + } + + RouteData data = new RouteData(); + data.setParts(parts); + + route.setData(data); + + context.addError("route", "Routing failed"); + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/GeoLocationPartEnricher.java b/src/main/java/net/contargo/iris/enricher/service/GeoLocationPartEnricher.java new file mode 100644 index 00000000..b223c141 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/GeoLocationPartEnricher.java @@ -0,0 +1,33 @@ + +package net.contargo.iris.enricher.service; + +import net.contargo.iris.address.nominatim.service.AddressResolutionException; +import net.contargo.iris.location.GeoLocationService; +import net.contargo.iris.route.RoutePart; + + +/** + * {@link RoutePartEnricher} for {@link net.contargo.iris.GeoLocation}s. + * + * @author Tobias Schneider - schneider@synyx.de + */ +class GeoLocationPartEnricher implements RoutePartEnricher { + + private final GeoLocationService geoLocationService; + + GeoLocationPartEnricher(GeoLocationService geoLocationService) { + + this.geoLocationService = geoLocationService; + } + + @Override + public void enrich(RoutePart part, EnricherContext context) throws CriticalEnricherException { + + try { + part.setOrigin(geoLocationService.getDetailedGeoLocation(part.getOrigin())); + part.setDestination(geoLocationService.getDetailedGeoLocation(part.getDestination())); + } catch (AddressResolutionException e) { + throw new CriticalEnricherException("Not possible to enrich geolocation", e); + } + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/MainRunPartEnricher.java b/src/main/java/net/contargo/iris/enricher/service/MainRunPartEnricher.java new file mode 100644 index 00000000..a8d78304 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/MainRunPartEnricher.java @@ -0,0 +1,62 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.connection.MainRunConnection; +import net.contargo.iris.connection.service.MainRunConnectionService; +import net.contargo.iris.distance.service.ConnectionDistanceService; +import net.contargo.iris.mainrun.service.MainRunDurationService; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RoutePartData; +import net.contargo.iris.route.RouteType; + + +/** + * Enricher to set information about distance and duration on a main run {@link RoutePart}. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Vincent Potucek - potucek@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +class MainRunPartEnricher implements RoutePartEnricher { + + private final MainRunConnectionService mainRunConnectionService; + private final MainRunDurationService mainRunDurationService; + private final ConnectionDistanceService connectionDistanceService; + + MainRunPartEnricher(MainRunConnectionService mainRunConnectionService, + MainRunDurationService mainRunDurationService, ConnectionDistanceService connectionDistanceService) { + + this.mainRunConnectionService = mainRunConnectionService; + this.mainRunDurationService = mainRunDurationService; + this.connectionDistanceService = connectionDistanceService; + } + + /** + * @see RoutePartEnricher#enrich(net.contargo.iris.route.RoutePart, EnricherContext) + */ + @Override + public void enrich(RoutePart routePart, EnricherContext context) throws CriticalEnricherException { + + if (routePart.isOfType(RouteType.BARGE) || routePart.isOfType(RouteType.RAIL)) { + RoutePartData routePartData = routePart.getData(); + + MainRunConnection mainRunConnection; + + try { + mainRunConnection = mainRunConnectionService.findRoutingConnectionBetweenTerminalAndSeaportByType( + routePart.findTerminal(), routePart.findSeaport(), routePart.getRouteType()); + + if (mainRunConnection == null) { + throw new IllegalStateException("mainRunConnection may not be null"); + } + } catch (IllegalStateException e) { + throw new CriticalEnricherException( + "Enriching not possible: missing terminal or seaport to determine main run connection", e); + } + + routePartData.setDistance(connectionDistanceService.getDistance(mainRunConnection)); + routePartData.setDieselDistance(connectionDistanceService.getDieselDistance(mainRunConnection)); + routePartData.setElectricDistance(connectionDistanceService.getElectricDistance(mainRunConnection)); + routePartData.setDuration(mainRunDurationService.getMainRunRoutePartDuration(mainRunConnection, routePart)); + } + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/RouteDataRevisionPartEnricher.java b/src/main/java/net/contargo/iris/enricher/service/RouteDataRevisionPartEnricher.java new file mode 100644 index 00000000..a35bceec --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/RouteDataRevisionPartEnricher.java @@ -0,0 +1,93 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.address.Address; +import net.contargo.iris.api.NotFoundException; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.routedatarevision.RouteDataRevision; +import net.contargo.iris.routedatarevision.service.RouteDataRevisionService; +import net.contargo.iris.terminal.Terminal; + +import org.slf4j.Logger; + +import java.lang.invoke.MethodHandles; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Enriches a {@link net.contargo.iris.route.RoutePart RoutePart} if the following conditions are met: the route parts + * direction is either terminal -> address or address -> terminal and the route parts type is TRUCK + * + * @author Tobias Schneider - schneider@synyx.de + */ +public class RouteDataRevisionPartEnricher implements RoutePartEnricher { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + private static final String MESSAGE = "Route part isn't Address -> Terminal or Terminal -> Address. " + + "So not applicable for route data revision."; + + private final RouteDataRevisionService routeDataRevisionService; + + public RouteDataRevisionPartEnricher(RouteDataRevisionService routeDataRevisionService) { + + this.routeDataRevisionService = routeDataRevisionService; + } + + @Override + public void enrich(RoutePart routePart, EnricherContext context) throws CriticalEnricherException { + + if (routePart.getRouteType() == RouteType.TRUCK) { + try { + Terminal terminal = extractTerminal(routePart); + Address address = extractAddress(routePart); + + RouteDataRevision routeDataRevision = routeDataRevisionService.getRouteDataRevision(terminal, address); + + if (routeDataRevision != null) { + routePart.getData().setDistance(routeDataRevision.getTruckDistanceOneWay()); + routePart.getData().setTollDistance(routeDataRevision.getTollDistanceOneWay()); + routePart.getData().setAirLineDistance(routeDataRevision.getAirlineDistance()); + } + } catch (NotFoundException e) { + LOG.debug(e.getMessage()); + } + } + } + + + private Terminal extractTerminal(RoutePart routePart) { + + Terminal terminal = null; + + if (routePart.getOrigin() instanceof Terminal) { + terminal = (Terminal) routePart.getOrigin(); + } else if (routePart.getDestination() instanceof Terminal) { + terminal = (Terminal) routePart.getDestination(); + } + + if (terminal == null) { + throw new NotFoundException(MESSAGE); + } + + return terminal; + } + + + private Address extractAddress(RoutePart routePart) { + + Address address = null; + + if (routePart.getOrigin() instanceof Address) { + address = (Address) routePart.getOrigin(); + } else if (routePart.getDestination() instanceof Address) { + address = (Address) routePart.getDestination(); + } + + if (address == null) { + throw new NotFoundException(MESSAGE); + } + + return address; + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/RoutePartEnricher.java b/src/main/java/net/contargo/iris/enricher/service/RoutePartEnricher.java new file mode 100644 index 00000000..c369f88f --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/RoutePartEnricher.java @@ -0,0 +1,20 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.route.RoutePart; + + +/** + * Interface for instances that can enrich {@link RoutePart} with more data. + * + * @author Tobias Schneider - schneider@synyx.de + */ +interface RoutePartEnricher extends Enricher { + + /** + * Enrich the {@link RoutePart} by any data. + * + * @param routePart the {@link RoutePart} to enrich + * @param context the {@link EnricherContext} + */ + void enrich(RoutePart routePart, EnricherContext context) throws CriticalEnricherException; +} diff --git a/src/main/java/net/contargo/iris/enricher/service/RouteTotalEnricher.java b/src/main/java/net/contargo/iris/enricher/service/RouteTotalEnricher.java new file mode 100644 index 00000000..fe6dc9bf --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/RouteTotalEnricher.java @@ -0,0 +1,20 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.route.Route; + + +/** + * Interface for instances that can enrich {@link Route} with more data. + * + * @author Tobias Schneider - schneider@synyx.de + */ +interface RouteTotalEnricher extends Enricher { + + /** + * Enrich the {@link Route} by any data. + * + * @param route to enrich + * @param context the context + */ + void enrich(Route route, EnricherContext context) throws CriticalEnricherException; +} diff --git a/src/main/java/net/contargo/iris/enricher/service/TotalEnricher.java b/src/main/java/net/contargo/iris/enricher/service/TotalEnricher.java new file mode 100644 index 00000000..3421948e --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/TotalEnricher.java @@ -0,0 +1,64 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteData; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RoutePartData; + +import java.math.BigDecimal; + + +/** + * Enricher that sums up the distances and total distances of a {@link Route} and set it as {@link RouteData} + * information. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Tobias Schneider - schneider@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +class TotalEnricher implements RouteTotalEnricher { + + @Override + public void enrich(Route route, EnricherContext context) { + + RouteData routeData = route.getData(); + + BigDecimal totalDistance = null; + BigDecimal totalRealTollDistance = null; + BigDecimal totalDuration = null; + + for (RoutePart part : routeData.getParts()) { + RoutePartData data = part.getData(); + + totalDistance = add(totalDistance, data.getDistance()); + totalRealTollDistance = add(totalRealTollDistance, data.getTollDistance()); + totalDuration = add(totalDuration, data.getDuration()); + } + + routeData.setTotalDistance(totalDistance); + routeData.setTotalRealTollDistance(totalRealTollDistance); + routeData.setTotalDuration(totalDuration); + } + + + private BigDecimal add(BigDecimal sum, BigDecimal summand) { + + BigDecimal result = sum; + + if (notNull(summand)) { + if (sum == null) { + result = BigDecimal.ZERO; + } + + result = result.add(summand); + } + + return result; + } + + + private boolean notNull(BigDecimal subject) { + + return subject != null; + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/TruckRoutingPartEnricher.java b/src/main/java/net/contargo/iris/enricher/service/TruckRoutingPartEnricher.java new file mode 100644 index 00000000..b7159860 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/TruckRoutingPartEnricher.java @@ -0,0 +1,58 @@ + +package net.contargo.iris.enricher.service; + +import net.contargo.iris.distance.service.DistanceService; +import net.contargo.iris.duration.service.DurationService; +import net.contargo.iris.osrm.service.RoutingException; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RoutePartData; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.truck.TruckRoute; +import net.contargo.iris.truck.service.TruckRouteService; + +import java.math.BigDecimal; + + +/** + * Enricher to set information about distance and duration on a Truck {@link RoutePart}. + * + * @author Tobias Schneider - schneider@synyx.de + */ +class TruckRoutingPartEnricher implements RoutePartEnricher { + + private final TruckRouteService truckRouteService; + private final DistanceService distanceService; + private final DurationService durationService; + + TruckRoutingPartEnricher(TruckRouteService truckRouteService, DistanceService distanceService, + DurationService durationService) { + + this.truckRouteService = truckRouteService; + this.distanceService = distanceService; + this.durationService = durationService; + } + + @Override + public void enrich(RoutePart routePart, EnricherContext context) throws CriticalEnricherException { + + if (routePart.isOfType(RouteType.TRUCK)) { + RoutePartData routePartData = routePart.getData(); + + TruckRoute truckRoute; + + try { + truckRoute = truckRouteService.route(routePart.getOrigin(), routePart.getDestination()); + } catch (RoutingException e) { + throw new CriticalEnricherException("Truck routing failed", e); + } + + BigDecimal partDistance = distanceService.getDistance(truckRoute); + + routePartData.setTollDistance(distanceService.getTollDistance(truckRoute)); + routePartData.setDistance(partDistance); + routePartData.setDieselDistance(partDistance); + routePartData.setElectricDistance(BigDecimal.ZERO); + routePartData.setDuration(durationService.getDuration(truckRoute)); + } + } +} diff --git a/src/main/java/net/contargo/iris/enricher/service/TruckingTotalEnricher.java b/src/main/java/net/contargo/iris/enricher/service/TruckingTotalEnricher.java new file mode 100644 index 00000000..305e5c67 --- /dev/null +++ b/src/main/java/net/contargo/iris/enricher/service/TruckingTotalEnricher.java @@ -0,0 +1,59 @@ +package net.contargo.iris.enricher.service; + +import net.contargo.iris.route.Route; +import net.contargo.iris.route.RouteData; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RoutePartData; +import net.contargo.iris.route.RouteType; + +import java.math.BigDecimal; + +import java.util.List; + + +/** + * Enricher that sums up the totalTollDistance of a {@link Route} and set it as {@link RouteData} information. + * + * @author Tobias Schneider - schneider@synyx.de + * @author Arnold Franke - franke@synyx.de + */ +class TruckingTotalEnricher implements RouteTotalEnricher { + + private static final BigDecimal DOUBLING = new BigDecimal("2"); + private static final BigDecimal DIVISOR = new BigDecimal("2"); + + @Override + public void enrich(Route route, EnricherContext context) { + + RouteData routeData = route.getData(); + + BigDecimal totalTollDistance = BigDecimal.ZERO; + BigDecimal totalOnewayTruckDistance = BigDecimal.ZERO; + + for (RoutePart truckPart : getRelevantTruckRouteParts(route)) { + RoutePartData truckPartData = truckPart.getData(); + + totalTollDistance = totalTollDistance.add(truckPartData.getTollDistance()); + totalOnewayTruckDistance = totalOnewayTruckDistance.add(truckPartData.getDistance()); + } + + if (route.isTriangle()) { + totalTollDistance = totalTollDistance.divide(DIVISOR); + totalOnewayTruckDistance = totalOnewayTruckDistance.divide(DIVISOR); + } + + // has to be doubled because above you look only at the oneway + routeData.setTotalTollDistance(totalTollDistance.multiply(DOUBLING)); + routeData.setTotalOnewayTruckDistance(totalOnewayTruckDistance); + } + + + private List getRelevantTruckRouteParts(Route route) { + + if (route.isTriangle()) { + return route.getData().getRoutePartsOfType(RouteType.TRUCK); + } + + return route.getData().getOnewayTruckParts().getTruckRoutePartList(); + } +} diff --git a/src/main/java/net/contargo/iris/gis/api/AirlineDistanceApiController.java b/src/main/java/net/contargo/iris/gis/api/AirlineDistanceApiController.java new file mode 100644 index 00000000..79dde4b1 --- /dev/null +++ b/src/main/java/net/contargo/iris/gis/api/AirlineDistanceApiController.java @@ -0,0 +1,108 @@ +package net.contargo.iris.gis.api; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; + +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.gis.dto.AirlineDistanceDto; +import net.contargo.iris.gis.dto.AirlineDistanceDtoService; + +import org.slf4j.Logger; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.lang.invoke.MethodHandles; + +import java.math.BigDecimal; + +import static org.slf4j.LoggerFactory.getLogger; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + + +/** + * Implements an RESTful geocode interface. + * + * @author Oliver Messner - messner@synyx.de + */ +@Api(value = AirlineDistanceApiController.AIRLINE_DISTANCE, description = "API for calculating airline distances.") +@Controller +@RequestMapping(AirlineDistanceApiController.AIRLINE_DISTANCE) +public class AirlineDistanceApiController extends AbstractController { + + static final String AIRLINE_DISTANCE = "airlineDistance"; + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + private static final BigDecimal LAT_MIN = new BigDecimal(-90); + private static final BigDecimal LAT_MAX = new BigDecimal(90); + private static final BigDecimal LON_MIN = new BigDecimal(-180); + private static final BigDecimal LON_MAX = new BigDecimal(180); + + private final AirlineDistanceDtoService airlineDistanceDtoService; + private final AirlineDistanceResponseAssembler responseAssembler; + + @Autowired + public AirlineDistanceApiController(AirlineDistanceDtoService airlineDistanceDtoService, + AirlineDistanceResponseAssembler responseAssembler) { + + this.airlineDistanceDtoService = airlineDistanceDtoService; + this.responseAssembler = responseAssembler; + } + + @ApiOperation( + value = "Calculates the airline distance between two geographical points in meters.", + notes = "Calculates the airline distance between two geographical points in meters." + ) + @RequestMapping(method = GET) + @ModelAttribute(AIRLINE_DISTANCE) + public AirlineDistanceResponse airlineDistance(@RequestParam("alat") String aLatitude, + @RequestParam("alon") String aLongitude, + @RequestParam("blat") String bLatitude, + @RequestParam("blon") String bLongitude) { + + validateGeoCoordinates(aLatitude, aLongitude, bLatitude, bLongitude); + + AirlineDistanceDto airlineDistanceDto = airlineDistanceDtoService.calcAirLineDistInMeters(aLatitude, aLongitude, + bLatitude, bLongitude); + LOG.info("API: Responding to request for airline distance between geographic locations"); + + return responseAssembler.toResource(airlineDistanceDto); + } + + + private void validateGeoCoordinates(String alat, String alon, String blat, String blon) { + + validateLatitude(alat); + validateLongitude(alon); + validateLatitude(blat); + validateLongitude(blon); + } + + + private void validateLatitude(String lat) { + + // may throw a NumberFormatException + BigDecimal latitude = new BigDecimal(lat); + + if (latitude.compareTo(LAT_MIN) == -1 || latitude.compareTo(LAT_MAX) == 1) { + throw new IllegalArgumentException(); + } + } + + + private void validateLongitude(String lon) { + + // may throw a NumberFormatException + BigDecimal longitude = new BigDecimal(lon); + + if (longitude.compareTo(LON_MIN) == -1 || longitude.compareTo(LON_MAX) == 1) { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/net/contargo/iris/gis/api/AirlineDistanceResponse.java b/src/main/java/net/contargo/iris/gis/api/AirlineDistanceResponse.java new file mode 100644 index 00000000..7aff0885 --- /dev/null +++ b/src/main/java/net/contargo/iris/gis/api/AirlineDistanceResponse.java @@ -0,0 +1,34 @@ +package net.contargo.iris.gis.api; + +import net.contargo.iris.gis.dto.AirlineDistanceDto; + +import org.springframework.hateoas.ResourceSupport; + +import java.math.BigDecimal; + + +/** + * @author Oliver Messner - messner@synyx.de + */ +class AirlineDistanceResponse extends ResourceSupport { + + private final BigDecimal distance; + private final String unit; + + public AirlineDistanceResponse(AirlineDistanceDto airlineDistanceDto) { + + this.distance = airlineDistanceDto.getDistance(); + this.unit = airlineDistanceDto.getUnit().getName(); + } + + public BigDecimal getDistance() { + + return distance; + } + + + public String getUnit() { + + return unit; + } +} diff --git a/src/main/java/net/contargo/iris/gis/api/AirlineDistanceResponseAssembler.java b/src/main/java/net/contargo/iris/gis/api/AirlineDistanceResponseAssembler.java new file mode 100644 index 00000000..9a584e6e --- /dev/null +++ b/src/main/java/net/contargo/iris/gis/api/AirlineDistanceResponseAssembler.java @@ -0,0 +1,38 @@ +package net.contargo.iris.gis.api; + +import net.contargo.iris.gis.dto.AirlineDistanceDto; + +import org.springframework.hateoas.Link; +import org.springframework.hateoas.mvc.ResourceAssemblerSupport; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + + +/** + * {@link ResourceAssemblerSupport} that maps an {@link net.contargo.iris.gis.dto.AirlineDistanceDto} to an object of + * type {@link AirlineDistanceResponse}. + * + * @author Oliver Messner - messner@synyx.de + */ +class AirlineDistanceResponseAssembler extends ResourceAssemblerSupport { + + public AirlineDistanceResponseAssembler() { + + super(AirlineDistanceApiController.class, AirlineDistanceResponse.class); + } + + @Override + public AirlineDistanceResponse toResource(AirlineDistanceDto airlineDistanceDto) { + + AirlineDistanceResponse airlineDistanceResponse = new AirlineDistanceResponse(airlineDistanceDto); + + Link linkWithoutParams = linkTo(AirlineDistanceApiController.class).withSelfRel(); + + // Did not find a way to attach ?-params properly with ControllerLinkBuilder + Link linkWithParams = new Link(linkWithoutParams.getHref().concat(airlineDistanceDto.getScope()), + linkWithoutParams.getRel()); + airlineDistanceResponse.add(linkWithParams); + + return airlineDistanceResponse; + } +} diff --git a/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceDto.java b/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceDto.java new file mode 100644 index 00000000..ec49991d --- /dev/null +++ b/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceDto.java @@ -0,0 +1,42 @@ +package net.contargo.iris.gis.dto; + +import java.math.BigDecimal; + + +/** + * DTO class that encapsulates information about airline distance and its unit. Also contains the scope information that + * may be accessed when this DTO gets mapped to an object of type + * {@link net.contargo.iris.gis.api.AirlineDistanceResponse}. + * + * @author Oliver Messner - messner@synyx.de + */ +public class AirlineDistanceDto { + + private final BigDecimal distance; + private final AirlineDistanceUnit unit; + private final String scope; + + public AirlineDistanceDto(BigDecimal distance, AirlineDistanceUnit unit, String scope) { + + this.distance = distance; + this.unit = unit; + this.scope = scope; + } + + public BigDecimal getDistance() { + + return distance; + } + + + public AirlineDistanceUnit getUnit() { + + return unit; + } + + + public String getScope() { + + return scope; + } +} diff --git a/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceDtoService.java b/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceDtoService.java new file mode 100644 index 00000000..fc9b3753 --- /dev/null +++ b/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceDtoService.java @@ -0,0 +1,22 @@ +package net.contargo.iris.gis.dto; + +/** + * Delegates service calls to the injected {@link GisService}. + * + * @author Oliver Messner - messner@synyx.de + */ +public interface AirlineDistanceDtoService { + + /** + * Calculates the airline distance between two geographical points (each represented by latitude and longitude) in + * meters. + * + * @param alat latitude of point A + * @param alon longitude of point A + * @param blat latitude of point B + * @param blon longitude of point B + * + * @return an object of type {@link AirlineDistanceDto} representing the airline distance + */ + AirlineDistanceDto calcAirLineDistInMeters(String alat, String alon, String blat, String blon); +} diff --git a/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceDtoServiceImpl.java b/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceDtoServiceImpl.java new file mode 100644 index 00000000..737f212a --- /dev/null +++ b/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceDtoServiceImpl.java @@ -0,0 +1,42 @@ +package net.contargo.iris.gis.dto; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.gis.service.GisService; + +import java.math.BigDecimal; + +import static net.contargo.iris.gis.dto.AirlineDistanceUnit.METER; + + +/** + * @author Oliver Messner - messner@synyx.de + */ +public class AirlineDistanceDtoServiceImpl implements AirlineDistanceDtoService { + + private GisService gisService; + + AirlineDistanceDtoServiceImpl() { + + // a nor-arg constructor must be available + } + + + public AirlineDistanceDtoServiceImpl(GisService gisService) { + + this.gisService = gisService; + } + + @Override + public AirlineDistanceDto calcAirLineDistInMeters(String alat, String alon, String blat, String blon) { + + // the scope identifies the distance that gets calculated + StringBuilder scope = new StringBuilder(); + scope.append("?alat=").append(alat).append("&alon=").append(alon); + scope.append("&blat=").append(blat).append("&blon=").append(blon); + + GeoLocation a = new GeoLocation(new BigDecimal(alat), new BigDecimal(alon)); + GeoLocation b = new GeoLocation(new BigDecimal(blat), new BigDecimal(blon)); + + return new AirlineDistanceDto(gisService.calcAirLineDistInMeters(a, b), METER, scope.toString()); + } +} diff --git a/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceUnit.java b/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceUnit.java new file mode 100644 index 00000000..07c6228f --- /dev/null +++ b/src/main/java/net/contargo/iris/gis/dto/AirlineDistanceUnit.java @@ -0,0 +1,21 @@ +package net.contargo.iris.gis.dto; + +/** + * @author Oliver Messner - messner@synyx.de + */ +public enum AirlineDistanceUnit { + + METER("meter"); + + private final String name; + + private AirlineDistanceUnit(String name) { + + this.name = name; + } + + public String getName() { + + return name; + } +} diff --git a/src/main/java/net/contargo/iris/gis/service/GisService.java b/src/main/java/net/contargo/iris/gis/service/GisService.java new file mode 100644 index 00000000..a2ae2501 --- /dev/null +++ b/src/main/java/net/contargo/iris/gis/service/GisService.java @@ -0,0 +1,27 @@ +package net.contargo.iris.gis.service; + +import net.contargo.iris.GeoLocation; + +import java.math.BigDecimal; + + +/** + * Provides geocode-related services. + * + * @author Sven Mueller - mueller@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public interface GisService { + + GeoLocation CENTER_OF_THE_EUROPEAN_UNION = new GeoLocation(new BigDecimal("50.1725"), new BigDecimal("9.15")); + + /** + * Calculates the airline distance between two geographical points in meters. + * + * @param a The first geographic location + * @param b The second geographic location + * + * @return The distance between a and b, in meters + */ + BigDecimal calcAirLineDistInMeters(GeoLocation a, GeoLocation b); +} diff --git a/src/main/java/net/contargo/iris/gis/service/GisServiceImpl.java b/src/main/java/net/contargo/iris/gis/service/GisServiceImpl.java new file mode 100644 index 00000000..99687fe5 --- /dev/null +++ b/src/main/java/net/contargo/iris/gis/service/GisServiceImpl.java @@ -0,0 +1,24 @@ +package net.contargo.iris.gis.service; + +import net.contargo.iris.GeoLocation; + +import org.geotools.referencing.GeodeticCalculator; + +import java.math.BigDecimal; + + +/** + * @author Sven Mueller - mueller@synyx.de + */ +public class GisServiceImpl implements GisService { + + @Override + public BigDecimal calcAirLineDistInMeters(GeoLocation a, GeoLocation b) { + + GeodeticCalculator calculator = new GeodeticCalculator(); + calculator.setStartingGeographicPoint(a.getLongitude().doubleValue(), a.getLatitude().doubleValue()); + calculator.setDestinationGeographicPoint(b.getLongitude().doubleValue(), b.getLatitude().doubleValue()); + + return new BigDecimal(calculator.getOrthodromicDistance()); + } +} diff --git a/src/main/java/net/contargo/iris/location/GeoLocationService.java b/src/main/java/net/contargo/iris/location/GeoLocationService.java new file mode 100644 index 00000000..ecc90d7b --- /dev/null +++ b/src/main/java/net/contargo/iris/location/GeoLocationService.java @@ -0,0 +1,21 @@ +package net.contargo.iris.location; + +import net.contargo.iris.GeoLocation; + + +/** + * Provides services related to {@link net.contargo.iris.GeoLocation} entities. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public interface GeoLocationService { + + /** + * Tries to find a detailed geolocation by a geolocation with only the gps data. + * + * @param location {@link GeoLocation} with only longitude and latitude + * + * @return Full {@link GeoLocation} with all information about this place + */ + GeoLocation getDetailedGeoLocation(GeoLocation location); +} diff --git a/src/main/java/net/contargo/iris/location/GeoLocationServiceImpl.java b/src/main/java/net/contargo/iris/location/GeoLocationServiceImpl.java new file mode 100644 index 00000000..0dcb08a6 --- /dev/null +++ b/src/main/java/net/contargo/iris/location/GeoLocationServiceImpl.java @@ -0,0 +1,64 @@ +package net.contargo.iris.location; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.address.Address; +import net.contargo.iris.address.service.AddressServiceWrapper; +import net.contargo.iris.seaport.service.SeaportService; +import net.contargo.iris.terminal.service.TerminalService; + +import org.slf4j.Logger; + +import java.lang.invoke.MethodHandles; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Provides implementations related to {@link net.contargo.iris.GeoLocation} entities. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public class GeoLocationServiceImpl implements GeoLocationService { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private final TerminalService terminalService; + private final SeaportService seaportService; + private final AddressServiceWrapper addressServiceWrapper; + + public GeoLocationServiceImpl(TerminalService terminalService, SeaportService seaportService, + AddressServiceWrapper addressServiceWrapper) { + + this.terminalService = terminalService; + this.seaportService = seaportService; + this.addressServiceWrapper = addressServiceWrapper; + } + + @Override + public GeoLocation getDetailedGeoLocation(GeoLocation location) { + + GeoLocation enrichedLocation = seaportService.getByGeoLocation(location); + + if (enrichedLocation == null) { + enrichedLocation = terminalService.getByGeoLocation(location); + } + + // try to resolve address from cache + if (enrichedLocation == null) { + Address address = addressServiceWrapper.getAddressForGeoLocation(location); + + if (null == address) { + LOG.warn("Couldn't resolve address for {}", location); + } else { + enrichedLocation = address; + } + } + + // if nothing found, set given location to enriched one + if (enrichedLocation == null) { + enrichedLocation = location; + } + + return enrichedLocation; + } +} diff --git a/src/main/java/net/contargo/iris/login/web/LoginController.java b/src/main/java/net/contargo/iris/login/web/LoginController.java new file mode 100644 index 00000000..bbc3acc3 --- /dev/null +++ b/src/main/java/net/contargo/iris/login/web/LoginController.java @@ -0,0 +1,29 @@ +package net.contargo.iris.login.web; + +import net.contargo.iris.api.AbstractController; + +import org.springframework.stereotype.Controller; + +import org.springframework.web.bind.annotation.RequestMapping; + +import static net.contargo.iris.api.AbstractController.LOGIN; +import static net.contargo.iris.api.AbstractController.SLASH; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; + + +/** + * @author Jörg Alberto Hoffmann - hoffmann@synyx.de + */ +@Controller +@RequestMapping(value = SLASH + LOGIN) +public class LoginController extends AbstractController { + + private static final String CONTROLLER_CONTEXT = "login" + SLASH; + + @RequestMapping(value = "", method = GET) + public String getLogin() { + + return CONTROLLER_CONTEXT + LOGIN; + } +} diff --git a/src/main/java/net/contargo/iris/mainrun/service/MainRunDurationService.java b/src/main/java/net/contargo/iris/mainrun/service/MainRunDurationService.java new file mode 100644 index 00000000..752d599a --- /dev/null +++ b/src/main/java/net/contargo/iris/mainrun/service/MainRunDurationService.java @@ -0,0 +1,23 @@ +package net.contargo.iris.mainrun.service; + +import net.contargo.iris.connection.MainRunConnection; +import net.contargo.iris.route.RoutePart; + +import java.math.BigDecimal; + + +/** + * @author Sven Mueller - mueller@synyx.de + */ +public interface MainRunDurationService { + + /** + * Computes the duration of a {@link RoutePart}. + * + * @param routePart a {@link RoutePart} + * @param mainrunConnection + * + * @return the duration of {@code routePart} if it's a main run, 0 if it's not + */ + BigDecimal getMainRunRoutePartDuration(MainRunConnection mainrunConnection, RoutePart routePart); +} diff --git a/src/main/java/net/contargo/iris/mainrun/service/MainRunDurationServiceImpl.java b/src/main/java/net/contargo/iris/mainrun/service/MainRunDurationServiceImpl.java new file mode 100644 index 00000000..477ee517 --- /dev/null +++ b/src/main/java/net/contargo/iris/mainrun/service/MainRunDurationServiceImpl.java @@ -0,0 +1,65 @@ +package net.contargo.iris.mainrun.service; + +import net.contargo.iris.connection.MainRunConnection; +import net.contargo.iris.rounding.RoundingService; +import net.contargo.iris.route.RoutePart; +import net.contargo.iris.route.RouteType; + +import java.math.BigDecimal; +import java.math.RoundingMode; + + +/** + * Computes duration of main runs and rounds results. + * + * @author Sven Mueller - mueller@synyx.de + * @author Aljona Murygina - murygina@synyx.de + * @author Sandra Thieme - thieme@synyx.de + */ +public class MainRunDurationServiceImpl implements MainRunDurationService { + + // average speeds in km/h + private static final BigDecimal AVERAGE_SPEED_BARGE_UPSTREAM = new BigDecimal("10.0"); + private static final BigDecimal AVERAGE_SPEED_BARGE_DOWNSTREAM = new BigDecimal("18.0"); + private static final BigDecimal AVERAGE_SPEED_RAIL = new BigDecimal("45.0"); + private static final BigDecimal SECONDS_IN_AN_HOUR = new BigDecimal("60.0"); + private static final int SCALE = 5; + + private final RoundingService roundingService; + + public MainRunDurationServiceImpl(RoundingService roundingService) { + + this.roundingService = roundingService; + } + + /** + * @see MainRunDurationService#getMainRunRoutePartDuration(net.contargo.iris.connection.MainRunConnection, + * net.contargo.iris.route.RoutePart) + */ + @Override + public BigDecimal getMainRunRoutePartDuration(MainRunConnection mainrunConnection, RoutePart routePart) { + + RoutePart.Direction direction = routePart.getDirection(); + BigDecimal distance = mainrunConnection.getTotalDistance(); + BigDecimal divisor; + + if (routePart.isOfType(RouteType.BARGE)) { + // electric distance is 0.0 if route part is of type barge, so you don't need to consider it in the + // calculation + if (direction == RoutePart.Direction.DOWNSTREAM) { + divisor = AVERAGE_SPEED_BARGE_DOWNSTREAM; + } else { + divisor = AVERAGE_SPEED_BARGE_UPSTREAM; + } + } else if (routePart.isOfType(RouteType.RAIL)) { + divisor = AVERAGE_SPEED_RAIL; + } else { + // not a main run route part, return 0.0 + return BigDecimal.ZERO; + } + + BigDecimal duration = distance.divide(divisor, SCALE, RoundingMode.HALF_UP).multiply(SECONDS_IN_AN_HOUR); + + return roundingService.roundDuration(duration); + } +} diff --git a/src/main/java/net/contargo/iris/normalizer/NormalizerService.java b/src/main/java/net/contargo/iris/normalizer/NormalizerService.java new file mode 100644 index 00000000..ef180c24 --- /dev/null +++ b/src/main/java/net/contargo/iris/normalizer/NormalizerService.java @@ -0,0 +1,18 @@ +package net.contargo.iris.normalizer; + +/** + * Provides services to normalize Strings. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public interface NormalizerService { + + /** + * Normalizes a given string by replacing umlaut, whitespaces, ... + * + * @param text to normalize + * + * @return normalized string + */ + String normalize(String text); +} diff --git a/src/main/java/net/contargo/iris/normalizer/NormalizerServiceImpl.java b/src/main/java/net/contargo/iris/normalizer/NormalizerServiceImpl.java new file mode 100644 index 00000000..c29f5bdd --- /dev/null +++ b/src/main/java/net/contargo/iris/normalizer/NormalizerServiceImpl.java @@ -0,0 +1,92 @@ +package net.contargo.iris.normalizer; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * Normalizer for iris related normalizations. + * + *
    + *
  • ä --> ae, ö --> oe, ü --> ue
  • + *
  • ß --> ss
  • + *
  • only chars, no numbers, hyphens, etc.
  • + *
  • to upper case
  • + *
+ * + * @author Michael Herbold - herbold@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +public class NormalizerServiceImpl implements NormalizerService { + + private final CustomNormalizer normalizerUE = new CustomNormalizer("[üÜ]", "ue"); + private final CustomNormalizer normalizerOE = new CustomNormalizer("[öÖ]", "oe"); + private final CustomNormalizer normalizerAE = new CustomNormalizer("[äÄ]", "ae"); + private final CustomNormalizer normalizerSS = new CustomNormalizer("ß", "ss"); + private final CustomNormalizer normalizerRemoveNumbers = new CustomNormalizer("\\d", ""); + private final CustomNormalizer normalizerRemovePunct = new CustomNormalizer("\\p{Punct}", ""); + private final CustomNormalizer normalizerRemoveWhitespaces = new CustomNormalizer("\\s", ""); + + @Override + public String normalize(String text) { + + if (text == null || text.isEmpty()) { + return text; + } + + String normalizedText; + + normalizedText = replace(text, normalizerUE); + normalizedText = replace(normalizedText, normalizerOE); + normalizedText = replace(normalizedText, normalizerAE); + normalizedText = replace(normalizedText, normalizerSS); + normalizedText = replace(normalizedText, normalizerRemoveNumbers); + normalizedText = replace(normalizedText, normalizerRemovePunct); + normalizedText = replace(normalizedText, normalizerRemoveWhitespaces); + + normalizedText = normalizedText.toUpperCase(Locale.getDefault()); + + return normalizedText; + } + + + private String replace(String text, CustomNormalizer normalizer) { + + Matcher matcher = Pattern.compile(normalizer.getRegex()).matcher(text); + + StringBuffer stringBuffer = new StringBuffer(); + + while (matcher.find()) { + matcher.appendReplacement(stringBuffer, normalizer.getReplacement()); + } + + matcher.appendTail(stringBuffer); + + return stringBuffer.toString(); + } + + private static final class CustomNormalizer { + + private final String regex; + + private final String replacement; + + private CustomNormalizer(String regex, String replacement) { + + this.regex = regex; + this.replacement = replacement; + } + + public String getRegex() { + + return regex; + } + + + public String getReplacement() { + + return replacement; + } + } +} diff --git a/src/main/java/net/contargo/iris/osrm/service/OSRMJsonResponse.java b/src/main/java/net/contargo/iris/osrm/service/OSRMJsonResponse.java new file mode 100644 index 00000000..c02c0017 --- /dev/null +++ b/src/main/java/net/contargo/iris/osrm/service/OSRMJsonResponse.java @@ -0,0 +1,70 @@ +package net.contargo.iris.osrm.service; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + + +/** + * Response of the osrm server. This response contains two important information. The first are the total kilometers and + * time which is provided in the route_summary attribute. And the total toll kilometers which are provided in the + * route_instructions. + * + * @author Sven Mueller - mueller@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@JsonIgnoreProperties({ "hint_data", "route_name", "via_indices", "found_alternative", "via_points" }) +class OSRMJsonResponse { + + private int status; + private String status_message; // NOSONAR Field is legacy part of public API + private OSRMJsonResponseRouteSummary route_summary; // NOSONAR Field is legacy part of public API + private String[][] route_instructions; // NOSONAR Field is legacy part of public API + + public String[][] getRoute_instructions() { // NOSONAR Field is part of public API, therefore unchangable legacy + + String[][] routeInstructions = route_instructions; + + return routeInstructions; + } + + + public void setRoute_instructions(String[][] routeInstructions) { // NOSONAR Field is legacy part of public API + + route_instructions = routeInstructions.clone(); + } + + + public int getStatus() { + + return status; + } + + + public void setStatus(int status) { + + this.status = status; + } + + + public String getStatus_message() { // NOSONAR Field is legacy part of public API + + return status_message; + } + + + public void setStatus_message(String statusMessage) { // NOSONAR Field is legacy part of public API + + this.status_message = statusMessage; + } + + + public OSRMJsonResponseRouteSummary getRoute_summary() { // NOSONAR Field is legacy part of public API + + return route_summary; + } + + + public void setRoute_summary(OSRMJsonResponseRouteSummary routeSummary) { // NOSONAR Field is part of public API + + this.route_summary = routeSummary; + } +} diff --git a/src/main/java/net/contargo/iris/osrm/service/OSRMJsonResponseRouteSummary.java b/src/main/java/net/contargo/iris/osrm/service/OSRMJsonResponseRouteSummary.java new file mode 100644 index 00000000..058a3064 --- /dev/null +++ b/src/main/java/net/contargo/iris/osrm/service/OSRMJsonResponseRouteSummary.java @@ -0,0 +1,73 @@ +package net.contargo.iris.osrm.service; + +import com.fasterxml.jackson.annotation.JsonProperty; + + +/** + * Reponse Object from the OSRM Route Summary. + * + * @author Tobias Schneider - schneider@synyx.de + */ +class OSRMJsonResponseRouteSummary { + + private static final String TOTAL_DISTANCE = "total_distance"; + private static final String TOTAL_TIME = "total_time"; + private static final String START_POINT = "start_point"; + private static final String END_POINT = "end_point"; + + @JsonProperty(TOTAL_DISTANCE) + private double totalDistance; + @JsonProperty(TOTAL_TIME) + private double totalTime; + @JsonProperty(START_POINT) + private String startPoint; + @JsonProperty(END_POINT) + private String endPoint; + + public double getTotal_distance() { // NOSONAR Field is legacy part of public API + + return totalDistance; + } + + + public void setTotalDistance(double totalDistance) { + + this.totalDistance = totalDistance; + } + + + public double getTotalTime() { + + return totalTime; + } + + + public void setTotalTime(double totalTime) { + + this.totalTime = totalTime; + } + + + public String getStart_point() { // NOSONAR Field is legacy part of public API + + return startPoint; + } + + + public void setStartPoint(String startPoint) { + + this.startPoint = startPoint; + } + + + public String getEnd_point() { // NOSONAR Field is legacy part of public API + + return endPoint; + } + + + public void setEndPoint(String endPoint) { + + this.endPoint = endPoint; + } +} diff --git a/src/main/java/net/contargo/iris/osrm/service/OSRMQueryResult.java b/src/main/java/net/contargo/iris/osrm/service/OSRMQueryResult.java new file mode 100644 index 00000000..95a7b25a --- /dev/null +++ b/src/main/java/net/contargo/iris/osrm/service/OSRMQueryResult.java @@ -0,0 +1,54 @@ +package net.contargo.iris.osrm.service; + +import java.util.Arrays; + + +/** + * Represents the result of a OSRM Query to the application. So we dont have to use the OSRM Response returned by the + * OSRM service + * + * @author Arnold Franke - franke@synyx.de + */ +public final class OSRMQueryResult { + + private final int status; + private final double totalDistance; + private final double totalTime; + private final String[][] routeInstructions; + + public OSRMQueryResult(int status, double totalDistance, double totalTime, String[][] instructions) { + + this.status = status; + this.totalDistance = totalDistance; + this.totalTime = totalTime; + + if (instructions == null) { + this.routeInstructions = new String[0][0]; + } else { + this.routeInstructions = instructions.clone(); + } + } + + public int getStatus() { + + return status; + } + + + public double getTotalDistance() { + + return totalDistance; + } + + + public double getTotalTime() { + + return totalTime; + } + + + public String[][] getRouteInstructions() { + + return Arrays.copyOf(routeInstructions, routeInstructions.length); + } +} diff --git a/src/main/java/net/contargo/iris/osrm/service/OSRMQueryService.java b/src/main/java/net/contargo/iris/osrm/service/OSRMQueryService.java new file mode 100644 index 00000000..3b640b61 --- /dev/null +++ b/src/main/java/net/contargo/iris/osrm/service/OSRMQueryService.java @@ -0,0 +1,23 @@ +package net.contargo.iris.osrm.service; + +import net.contargo.iris.GeoLocation; + + +/** + * Interface of the osrm services. + * + * @author Sven Mueller - mueller@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +public interface OSRMQueryService { + + /** + * Returns the information from osrm of the route between the start and the destination. + * + * @param start {@link GeoLocation }of the start point + * @param destination {@link GeoLocation } of the destination point + * + * @return {@link OSRMQueryResult} with all information given by the osrm server + */ + OSRMQueryResult getOSRMXmlRoute(GeoLocation start, GeoLocation destination); +} diff --git a/src/main/java/net/contargo/iris/osrm/service/OSRMQueryServiceImpl.java b/src/main/java/net/contargo/iris/osrm/service/OSRMQueryServiceImpl.java new file mode 100644 index 00000000..7f350169 --- /dev/null +++ b/src/main/java/net/contargo/iris/osrm/service/OSRMQueryServiceImpl.java @@ -0,0 +1,86 @@ +package net.contargo.iris.osrm.service; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.util.HttpUtil; +import net.contargo.iris.util.HttpUtilException; + +import java.io.IOException; + + +/** + * Implementation of the osrm query service. + * + * @author Sven Mueller - mueller@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +public class OSRMQueryServiceImpl implements OSRMQueryService { + + private static final int STATUS_NO_ROUTE = 207; + + private static final String Q_QUESTIONMARK = "?"; + private static final String Q_EQUALS = "="; + private static final String Q_AMPERSAND = "&"; + private static final String Q_COMMA = ","; + + private static final String Q_LOCATION = "loc"; + private static final String Q_ZOOM = "z"; + private static final String Q_GEOMETRY = "geometry"; + private static final String Q_INSTRUCTIONS = "instructions"; + private static final String Q_ALTERNATIVE = "alt"; + + private final String baseUrl; + private final HttpUtil httpUtil; + private final ObjectMapper objectMapper; + + public OSRMQueryServiceImpl(HttpUtil httpUtil, String baseUrl, ObjectMapper objectMapper) { + + this.httpUtil = httpUtil; + this.baseUrl = baseUrl; + this.objectMapper = objectMapper; + } + + @Override + public OSRMQueryResult getOSRMXmlRoute(GeoLocation start, GeoLocation destination) { + + try { + return createOSRMQueryResult(route(start, destination)); + } catch (HttpUtilException | IOException e) { + throw new RoutingException("Error querying OSRM service: ", e); + } + } + + + private OSRMJsonResponse route(GeoLocation start, GeoLocation destination) throws IOException { + + String query = createOSRMQueryString(start, destination); + String response = httpUtil.getResponseContent(query); + + return objectMapper.readValue(response, OSRMJsonResponse.class); + } + + + private String createOSRMQueryString(GeoLocation start, GeoLocation destination) { + + return baseUrl + Q_QUESTIONMARK + Q_LOCATION + Q_EQUALS + start.getLatitude() + Q_COMMA + start.getLongitude() + + Q_AMPERSAND + Q_LOCATION + Q_EQUALS + destination.getLatitude() + Q_COMMA + destination.getLongitude() + + Q_AMPERSAND + Q_ZOOM + Q_EQUALS + 0 + Q_AMPERSAND + Q_GEOMETRY + Q_EQUALS + false + Q_AMPERSAND + + Q_INSTRUCTIONS + Q_EQUALS + true + Q_AMPERSAND + Q_ALTERNATIVE + Q_EQUALS + false; + } + + + private OSRMQueryResult createOSRMQueryResult(OSRMJsonResponse response) { + + double totalDistance = 0d; + double totalTime = 0d; + int status = response.getStatus(); + + if (status != STATUS_NO_ROUTE) { + totalDistance = response.getRoute_summary().getTotal_distance(); + totalTime = response.getRoute_summary().getTotalTime(); + } + + return new OSRMQueryResult(status, totalDistance, totalTime, response.getRoute_instructions()); + } +} diff --git a/src/main/java/net/contargo/iris/osrm/service/RoutingException.java b/src/main/java/net/contargo/iris/osrm/service/RoutingException.java new file mode 100644 index 00000000..a2a9ef39 --- /dev/null +++ b/src/main/java/net/contargo/iris/osrm/service/RoutingException.java @@ -0,0 +1,17 @@ +/** + * + */ +package net.contargo.iris.osrm.service; + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +public class RoutingException extends RuntimeException { + + private static final long serialVersionUID = -659534689406599095L; + + public RoutingException(String message, Exception cause) { + + super(message, cause); + } +} diff --git a/src/main/java/net/contargo/iris/rounding/RoundingService.java b/src/main/java/net/contargo/iris/rounding/RoundingService.java new file mode 100644 index 00000000..782a6d2b --- /dev/null +++ b/src/main/java/net/contargo/iris/rounding/RoundingService.java @@ -0,0 +1,32 @@ +package net.contargo.iris.rounding; + +import java.math.BigDecimal; + + +/** + * Interface for instances that helps to round data informations. + * + * @author Tobias Schneider - schneider@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public interface RoundingService { + + int DIGITS_TO_CUT_DISTANCE = 3; + int DIGITS_TO_CUT_DURATION = 1; + int DIGITS_TO_ROUND = 0; + + /** + * @param bigDecimal BigDecimal to round + * + * @return bigDecimal rounded BigDecimal + */ + BigDecimal roundDistance(BigDecimal bigDecimal); + + + /** + * @param bigDecimal BigDecimal to round + * + * @return bigDecimal rounded BigDecimal + */ + BigDecimal roundDuration(BigDecimal bigDecimal); +} diff --git a/src/main/java/net/contargo/iris/rounding/RoundingServiceImpl.java b/src/main/java/net/contargo/iris/rounding/RoundingServiceImpl.java new file mode 100644 index 00000000..fd1447e3 --- /dev/null +++ b/src/main/java/net/contargo/iris/rounding/RoundingServiceImpl.java @@ -0,0 +1,27 @@ +package net.contargo.iris.rounding; + +import java.math.BigDecimal; + +import static java.math.BigDecimal.ROUND_DOWN; +import static java.math.BigDecimal.ROUND_UP; + + +/** + * @author Tobias Schneider - schneider@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public class RoundingServiceImpl implements RoundingService { + + @Override + public BigDecimal roundDistance(BigDecimal bigDecimal) { + + return bigDecimal.setScale(DIGITS_TO_CUT_DISTANCE, ROUND_DOWN).setScale(DIGITS_TO_ROUND, ROUND_UP); + } + + + @Override + public BigDecimal roundDuration(BigDecimal bigDecimal) { + + return bigDecimal.setScale(DIGITS_TO_CUT_DURATION, ROUND_DOWN).setScale(DIGITS_TO_ROUND, ROUND_UP); + } +} diff --git a/src/main/java/net/contargo/iris/route/DirectTruckRouteBuilder.java b/src/main/java/net/contargo/iris/route/DirectTruckRouteBuilder.java new file mode 100644 index 00000000..098820e9 --- /dev/null +++ b/src/main/java/net/contargo/iris/route/DirectTruckRouteBuilder.java @@ -0,0 +1,98 @@ +package net.contargo.iris.route; + +import net.contargo.iris.terminal.Terminal; +import net.contargo.iris.truck.TruckRoute; +import net.contargo.iris.truck.service.TruckRouteService; + +import java.math.BigDecimal; + + +/** + * Helper class to build a direct truck route from a given route. + * + * @author Aljona Murygina - murygina@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public class DirectTruckRouteBuilder { + + private final TruckRouteService truckRouteService; + + public DirectTruckRouteBuilder(TruckRouteService truckRouteService) { + + this.truckRouteService = truckRouteService; + } + + /** + * Builds the corresponding direct truck route to a given {@link Route} that may have mainrun parts. + * + * @param route {@link Route} + * + * @return corresponding direct truck route to the given {@link Route} + */ + public Route getCorrespondingDirectTruckRoute(Route route) { + + Route newRoute = new Route(); + + boolean skipLoop = false; + + for (int i = 0; i < route.getData().getParts().size(); i++) { + if (skipLoop) { + skipLoop = false; + + continue; + } + + RoutePart currentPart = route.getData().getParts().get(i); + RoutePart partToAdd = currentPart.copyWithoutData(); + + if (!isLastPart(route, i)) { + RoutePart nextPart = route.getData().getParts().get(i + 1); + + if (stopsOnTerminal(currentPart, nextPart)) { + partToAdd = currentPart.copyWithoutData(); + partToAdd.setDestination(nextPart.getDestination()); + partToAdd.setRouteType(RouteType.TRUCK); + + // skip over the next part + skipLoop = true; + } + } + + enrichRoutePartWithDistance(partToAdd); + newRoute.getData().getParts().add(partToAdd); + } + + return newRoute; + } + + + private boolean isLastPart(Route route, int i) { + + return i == route.getData().getParts().size() - 1; + } + + + private void enrichRoutePartWithDistance(RoutePart part) { + + TruckRoute route = truckRouteService.route(part.getOrigin(), part.getDestination()); + BigDecimal distance = route.getDistance(); + part.getData().setDistance(distance); + } + + + /** + * Checks if {@link Route} has a {@link Terminal} as stopover. (e.g. if {@link Route} has schema: Seaport --> + * Terminal --> Destination --> Terminal) + * + * @param currentPart {@link RoutePart} + * @param nextPart {@link RoutePart} + * + * @return true if {@link Route} has a {@link Terminal} as stopover, false if not (i.e. Terminal is end or start + * point, e.g.: Seaport --> Destination --> Terminal) + */ + private boolean stopsOnTerminal(RoutePart currentPart, RoutePart nextPart) { + + return (currentPart.getDestination() instanceof Terminal) + && currentPart.getDestination().equals(nextPart.getOrigin()); + } +} diff --git a/src/main/java/net/contargo/iris/route/Route.java b/src/main/java/net/contargo/iris/route/Route.java new file mode 100644 index 00000000..55c21f84 --- /dev/null +++ b/src/main/java/net/contargo/iris/route/Route.java @@ -0,0 +1,196 @@ +package net.contargo.iris.route; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import net.contargo.iris.GeoLocation; +import net.contargo.iris.container.ContainerState; +import net.contargo.iris.terminal.Terminal; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static net.contargo.iris.container.ContainerState.EMPTY; +import static net.contargo.iris.container.ContainerState.FULL; + + +/** + * Represents a combined routing, consisting of multiple RoutePart objects. + * + *

A typical example would be a Route consisting of a BargeRoute, followed by a TruckRoute.

+ * + * @author Sandra Thieme - thieme@synyx.de + */ +public class Route { + + private RouteData data = new RouteData(); + + private Terminal responsibleTerminal; + + private Map errors = new HashMap<>(); + + public RouteData getData() { + + return data; + } + + + public void setData(RouteData data) { + + this.data = data; + } + + + public Map getErrors() { + + return errors; + } + + + public Terminal getResponsibleTerminal() { + + return responsibleTerminal; + } + + + public void setResponsibleTerminal(Terminal responsibleTerminal) { + + this.responsibleTerminal = responsibleTerminal; + } + + + public void setErrors(Map errors) { + + this.errors = errors; + } + + + /** + * Creates a name for this {@link Route} out of its parts. + * + * @return a name representing this {@link Route} + */ + public String getName() { + + StringBuilder routeName = new StringBuilder(); + + for (int i = 0; i < getData().getParts().size(); i++) { + if (i > 0) { + routeName.append(" -> "); + } + + routeName.append(getData().getParts().get(i).getOrigin().getNiceName()); + + if (i == (getData().getParts().size() - 1)) { + routeName.append(" -> ").append(getData().getParts().get(i).getDestination().getNiceName()); + } + } + + return routeName.toString(); + } + + + public String getShortName() { + + for (RoutePart routePart : getData().getParts()) { + if (routePart.getOrigin() instanceof Terminal) { + return "via " + ((Terminal) routePart.getOrigin()).getName(); + } else if (routePart.getDestination() instanceof Terminal) { + return "via " + ((Terminal) routePart.getDestination()).getName(); + } + } + + return getName(); + } + + + public RouteProduct getProduct() { + + List routeParts = getData().getParts(); + + if (routeParts.isEmpty()) { + return RouteProduct.ONEWAY; + } + + // if first part and last part of route are identical, than the route is a round trip + GeoLocation start = routeParts.get(0).getOrigin(); + GeoLocation end = routeParts.get(routeParts.size() - 1).getDestination(); + + if (start.equals(end)) { + return RouteProduct.ROUNDTRIP; + } + + return RouteProduct.ONEWAY; + } + + + public boolean isRoundTrip() { + + return RouteProduct.ROUNDTRIP.equals(getProduct()); + } + + + public RouteDirection getDirection() { + + List parts = getData().getParts(); + + if (parts.isEmpty()) { + return null; + } + + ContainerState currentState = parts.get(0).getContainerState(); + RouteDirection direction = null; + + for (RoutePart part : parts) { + ContainerState newState = part.getContainerState(); + + if (null != newState && null != currentState) { + if (!currentState.equals(newState)) { + RouteDirection currentDirection = determineRouteDirection(currentState, newState); + + if (hasDirectionChanged(direction, currentDirection)) { + return null; + } else { + direction = currentDirection; + } + } + + currentState = newState; + } + } + + return direction; + } + + + private boolean hasDirectionChanged(RouteDirection direction, RouteDirection currentDirection) { + + return direction != null && !direction.equals(currentDirection); + } + + + private RouteDirection determineRouteDirection(ContainerState oldState, ContainerState newState) { + + if (EMPTY.equals(oldState) && FULL.equals(newState)) { + return RouteDirection.EXPORT; + } else if (FULL.equals(oldState) && EMPTY.equals(newState)) { + return RouteDirection.IMPORT; + } else { + return null; + } + } + + + /** + * Returns true if this route is an triangle routing or false if it is not. + * + * @return true if triangle routing, else false + */ + @JsonIgnore + public Boolean isTriangle() { + + TruckRouteParts onewayTruckParts = data.getOnewayTruckParts(); + List truckParts = data.getRoutePartsOfType(RouteType.TRUCK); + + return onewayTruckParts.getTruckRoutePartList().size() == truckParts.size(); + } +} diff --git a/src/main/java/net/contargo/iris/route/RouteBuilder.java b/src/main/java/net/contargo/iris/route/RouteBuilder.java new file mode 100644 index 00000000..7a7a9907 --- /dev/null +++ b/src/main/java/net/contargo/iris/route/RouteBuilder.java @@ -0,0 +1,83 @@ +package net.contargo.iris.route; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.container.ContainerState; +import net.contargo.iris.container.ContainerType; +import net.contargo.iris.terminal.Terminal; + + +/** + * Builder class to create a {@link Route} and enrich its {@link RoutePart}s only with important information like + * origin, destination, {@link ContainerType}, etc. + * + * @author Aljona Murygina - murygina@synyx.de + */ +public class RouteBuilder { + + private final Route route; + private GeoLocation currentLocation; + private ContainerType containerType; + private ContainerState containerState; + + public RouteBuilder(GeoLocation startLocation, ContainerType containerType, ContainerState containerState) { + + this.route = new Route(); + + this.currentLocation = startLocation; + this.containerType = containerType; + this.containerState = containerState; + } + + public void unloadContainer() { + + containerState = ContainerState.EMPTY; + } + + + public void loadContainer() { + + containerState = ContainerState.FULL; + } + + + /** + * Adds a {@link RoutePart} to {@link Route} setting information about origin, destination, route type, container + * type, container state (full or empty) and direction (if barge route). + * + * @param destination as next destination + * @param routeType type of the next part with destination + */ + public RouteBuilder goTo(GeoLocation destination, RouteType routeType) { + + RoutePart part = new RoutePart(currentLocation, destination, routeType); + + part.setContainerType(containerType); + part.setContainerState(containerState); + + route.getData().getParts().add(part); + + part.setData(null); + + currentLocation = destination; + + return this; + } + + + public void responsibleTerminal(Terminal terminal) { + + route.setResponsibleTerminal(terminal); + } + + + public Route getRoute() { + + return route; + } + + + public void changeContainerType(ContainerType type) { + + this.containerType = type; + } +} diff --git a/src/main/java/net/contargo/iris/route/RouteCombo.java b/src/main/java/net/contargo/iris/route/RouteCombo.java new file mode 100644 index 00000000..9a09bb77 --- /dev/null +++ b/src/main/java/net/contargo/iris/route/RouteCombo.java @@ -0,0 +1,29 @@ +package net.contargo.iris.route; + +/** + * Enum describing which combo mode a Route has. Each combo has one or more route types. + * + * @author Aljona Murygina - murygina@synyx.de + */ +public enum RouteCombo { + + WATERWAY(new RouteType[] { RouteType.BARGE }), + RAILWAY(new RouteType[] { RouteType.RAIL }), + DIRECT_TRUCK(new RouteType[] { RouteType.TRUCK }), + ALL(new RouteType[] { RouteType.BARGE, RouteType.RAIL }); + // DIRECT_TRUCK is at the moment not in ALL included. #3194 + + private RouteType[] routeTypes; + + private RouteCombo(RouteType[] routeTypes) { + + this.routeTypes = routeTypes.clone(); + } + + public RouteType[] getRouteTypes() { + + RouteType[] thisRouteTypes = routeTypes; + + return thisRouteTypes; + } +} diff --git a/src/main/java/net/contargo/iris/route/RouteData.java b/src/main/java/net/contargo/iris/route/RouteData.java new file mode 100644 index 00000000..a3ca3764 --- /dev/null +++ b/src/main/java/net/contargo/iris/route/RouteData.java @@ -0,0 +1,144 @@ +package net.contargo.iris.route; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * This class represents a collection of attributes associated with a {@link Route}. Any of these attributes might be + * set by one or more enricher object(s). Route data doesn't only consist of distance and duration related information, + * but also encompasses a list of {@link RoutePart} objects. + * + * @author Sven Mueller - mueller@synyx.de + * @author Tobias Schneider - schneider@synyx.de + * @author Oliver Messner - messner@synyx.de + * @see Route + * @see RoutePart + * @see RouteBuilder + */ +public class RouteData { + + private BigDecimal totalDistance; + private BigDecimal totalTollDistance; + private BigDecimal totalRealTollDistance; + private BigDecimal totalOnewayTruckDistance; + private BigDecimal totalDuration; + private BigDecimal co2; + private BigDecimal co2DirectTruck; + private List parts = new ArrayList<>(); + + public BigDecimal getTotalDistance() { + + return totalDistance; + } + + + public void setTotalDistance(BigDecimal totalDistance) { + + this.totalDistance = totalDistance; + } + + + public BigDecimal getTotalTollDistance() { + + return totalTollDistance; + } + + + public void setTotalTollDistance(BigDecimal totalTollDistance) { + + this.totalTollDistance = totalTollDistance; + } + + + public BigDecimal getTotalRealTollDistance() { + + return totalRealTollDistance; + } + + + public void setTotalRealTollDistance(BigDecimal totalRealTollDistance) { + + this.totalRealTollDistance = totalRealTollDistance; + } + + + public BigDecimal getTotalOnewayTruckDistance() { + + return totalOnewayTruckDistance; + } + + + public void setTotalOnewayTruckDistance(BigDecimal totalOnewayTruckDistance) { + + this.totalOnewayTruckDistance = totalOnewayTruckDistance; + } + + + public BigDecimal getTotalDuration() { + + return totalDuration; + } + + + public void setTotalDuration(BigDecimal totalDuration) { + + this.totalDuration = totalDuration; + } + + + public BigDecimal getCo2() { + + return co2; + } + + + public void setCo2(BigDecimal co2) { + + this.co2 = co2; + } + + + public BigDecimal getCo2DirectTruck() { + + return co2DirectTruck; + } + + + public void setCo2DirectTruck(BigDecimal co2DirectTruck) { + + this.co2DirectTruck = co2DirectTruck; + } + + + public List getParts() { + + return parts; + } + + + public void setParts(List parts) { + + this.parts = parts; + } + + + @JsonIgnore + public TruckRouteParts getOnewayTruckParts() { + + TruckRouteParts truckParts = new TruckRouteParts(getRoutePartsOfType(RouteType.TRUCK)); + + truckParts.reduceToOneway(); + + return truckParts; + } + + + public List getRoutePartsOfType(final RouteType routeType) { + + return parts.stream().filter(part -> part.isOfType(routeType)).collect(Collectors.toList()); + } +} diff --git a/src/main/java/net/contargo/iris/route/RouteDirection.java b/src/main/java/net/contargo/iris/route/RouteDirection.java new file mode 100644 index 00000000..4c373835 --- /dev/null +++ b/src/main/java/net/contargo/iris/route/RouteDirection.java @@ -0,0 +1,26 @@ +package net.contargo.iris.route; + +/** + * Direction of a Routing (is stuff exported or imported). + * + * @author Sandra Thieme - thieme@synyx.de + */ +public enum RouteDirection { + + IMPORT, + EXPORT; + + /** + * @param isImport boolean that indicates if it is an IMPORT + * + * @return IMPORT if isImport is true, EXPORT otherwise + */ + public static RouteDirection fromIsImport(boolean isImport) { + + if (isImport) { + return IMPORT; + } else { + return EXPORT; + } + } +} diff --git a/src/main/java/net/contargo/iris/route/RouteInformation.java b/src/main/java/net/contargo/iris/route/RouteInformation.java new file mode 100644 index 00000000..c93ac985 --- /dev/null +++ b/src/main/java/net/contargo/iris/route/RouteInformation.java @@ -0,0 +1,99 @@ +package net.contargo.iris.route; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.container.ContainerType; + + +/** + * Encapsulates detail information that are needed to compute a {@link net.contargo.iris.route.Route}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public class RouteInformation { + + private GeoLocation destination; + private RouteProduct product; + private ContainerType containerType; + private RouteDirection routeDirection; + private RouteCombo routeCombo; + + public RouteInformation(ContainerType containerType, RouteCombo combo, double lat, double lon, boolean isRoundTrip, + boolean isImport) { + + this.destination = new GeoLocation(lat, lon); + this.containerType = containerType; + this.routeCombo = combo; + this.product = RouteProduct.fromIsRoundtrip(isRoundTrip); + this.routeDirection = RouteDirection.fromIsImport(isImport); + } + + + public RouteInformation(GeoLocation destination, RouteProduct product, ContainerType containerType, + RouteDirection routeDirection, RouteCombo combo) { + + this.destination = destination; + this.product = product; + this.containerType = containerType; + this.routeDirection = routeDirection; + this.routeCombo = combo; + } + + public GeoLocation getDestination() { + + return destination; + } + + + public void setDestination(GeoLocation destination) { + + this.destination = destination; + } + + + public RouteProduct getProduct() { + + return product; + } + + + public void setProduct(RouteProduct product) { + + this.product = product; + } + + + public ContainerType getContainerType() { + + return containerType; + } + + + public void setContainerType(ContainerType containerType) { + + this.containerType = containerType; + } + + + public RouteDirection getRouteDirection() { + + return routeDirection; + } + + + public void setRouteDirection(RouteDirection routeDirection) { + + this.routeDirection = routeDirection; + } + + + public RouteCombo getRouteCombo() { + + return routeCombo; + } + + + public void setRouteCombo(RouteCombo routeCombo) { + + this.routeCombo = routeCombo; + } +} diff --git a/src/main/java/net/contargo/iris/route/RoutePart.java b/src/main/java/net/contargo/iris/route/RoutePart.java new file mode 100644 index 00000000..9757a7ef --- /dev/null +++ b/src/main/java/net/contargo/iris/route/RoutePart.java @@ -0,0 +1,236 @@ +package net.contargo.iris.route; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.container.ContainerState; +import net.contargo.iris.container.ContainerType; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.terminal.Terminal; + + +/** + * This class represents a route part having two associated {@link GeoLocation}s, each denoting an origin or a + * destination respectively. Note that the associated {@link GeoLocation}s may be any subtype of {@link GeoLocation}. + * + * @author Michael Herbold - herbold@synyx.de + * @author Oliver Messner - messner@synyx.de + * @see GeoLocation + */ +public class RoutePart { + + public enum Direction { + + NOT_SET, + DOWNSTREAM, + UPSTREAM + } + + private GeoLocation origin; + private GeoLocation destination; + private RouteType routeType; + private ContainerType containerType; + private ContainerState containerState; + private RoutePartData data = new RoutePartData(); + + public RoutePart() { + + // Needed for Spring MVC JSON mapping + } + + + public RoutePart(GeoLocation origin, GeoLocation destination, RouteType routeType) { + + this.origin = origin; + this.destination = destination; + this.routeType = routeType; + } + + + public RoutePart(RouteType routeType, GeoLocation origin, GeoLocation destination, ContainerType containerType, + ContainerState containerState) { + + this.routeType = routeType; + this.origin = origin; + this.destination = destination; + this.containerType = containerType; + this.containerState = containerState; + } + + /** + * Computes this {@link RoutePart}'s direction. + * + * @return
    + *
  • UPSTREAM if it's a {@link RoutePart} from {@link Seaport} to {@link Terminal}
  • + *
  • DOWNSTREAM if it's a {@link RoutePart} from {@link Terminal} to {@link Seaport}
  • + *
  • NOT_SET otherwise, or if it's not a main run part
  • + *
+ */ + public Direction getDirection() { + + if (!isOfType(RouteType.BARGE)) { + return Direction.NOT_SET; + } + + if (origin instanceof Seaport && destination instanceof Terminal) { + return RoutePart.Direction.UPSTREAM; + } else if (origin instanceof Terminal && destination instanceof Seaport) { + return Direction.DOWNSTREAM; + } else { + return Direction.NOT_SET; + } + } + + + public boolean isOfType(RouteType type) { + + return this.routeType == type && type != null; + } + + + public String getName() { + + return getOrigin().getNiceName() + " -> " + getDestination().getNiceName(); + } + + + public GeoLocation getOrigin() { + + return origin; + } + + + public void setOrigin(GeoLocation origin) { + + this.origin = origin; + } + + + public GeoLocation getDestination() { + + return destination; + } + + + public void setDestination(GeoLocation destination) { + + this.destination = destination; + } + + + public RouteType getRouteType() { + + return routeType; + } + + + public void setRouteType(RouteType routeType) { + + this.routeType = routeType; + } + + + public ContainerType getContainerType() { + + return containerType; + } + + + public void setContainerType(ContainerType containerType) { + + this.containerType = containerType; + } + + + public ContainerState getContainerState() { + + return containerState; + } + + + public void setContainerState(ContainerState containerState) { + + this.containerState = containerState; + } + + + public RoutePartData getData() { + + return data; + } + + + public void setData(RoutePartData data) { + + this.data = data; + } + + + /** + * Checks whether this {@link RoutePart} has a {@link Terminal}. + * + * @return true if origin or destination are a {@link Terminal}, false otherwise + */ + public boolean hasTerminal() { + + return getOrigin() instanceof Terminal || getDestination() instanceof Terminal; + } + + + /** + * Checks whether this {@link RoutePart} has a {@link Seaport}. + * + * @return true if origin or destination are a {@link Seaport}, false otherwise + */ + public boolean hasSeaport() { + + return getOrigin() instanceof Seaport || getDestination() instanceof Seaport; + } + + + /** + * Finds this {@link RoutePart}'s {@link Terminal}. + * + * @return the associated {@link Terminal} + * + * @throws IllegalStateException if neither origin nor destination are a {@link Terminal} + */ + public Terminal findTerminal() { + + if (getOrigin() instanceof Terminal) { + return (Terminal) getOrigin(); + } else if (getDestination() instanceof Terminal) { + return (Terminal) getDestination(); + } + + throw new IllegalStateException("Neither origin nor destination is a Teminal"); + } + + + /** + * Finds this {@link RoutePart}'s {@link Seaport}. + * + * @return the associated {@link Seaport} + * + * @throws IllegalStateException if neither origin nor destination are a {@link Seaport} + */ + public Seaport findSeaport() { + + if (getOrigin() instanceof Seaport) { + return (Seaport) getOrigin(); + } else if (getDestination() instanceof Seaport) { + return (Seaport) getDestination(); + } + + throw new IllegalStateException("Neither origin nor destination is a SeaPort"); + } + + + /** + * Returns a copy of the {@link RoutePart} without {@link RouteData}. + * + * @return {@link net.contargo.iris.route.RoutePart} without {@link net.contargo.iris.route.RouteData} + */ + public RoutePart copyWithoutData() { + + return new RoutePart(routeType, origin, destination, containerType, containerState); + } +} diff --git a/src/main/java/net/contargo/iris/route/RoutePartData.java b/src/main/java/net/contargo/iris/route/RoutePartData.java new file mode 100644 index 00000000..5ea8cc21 --- /dev/null +++ b/src/main/java/net/contargo/iris/route/RoutePartData.java @@ -0,0 +1,107 @@ +package net.contargo.iris.route; + +import java.math.BigDecimal; + + +/** + * This class represents a collection of attributes associated with a {@link RoutePart}. Any of these attributes might + * be set by (one or more) enricher object(s). + * + * @author Tobias Schneider - schneider@synyx.de + * @author Arnold Franke - franke@synyx.de + * @see RoutePart + */ +public class RoutePartData { + + private BigDecimal airLineDistance; + private BigDecimal distance; + private BigDecimal dieselDistance; + private BigDecimal electricDistance; + private BigDecimal tollDistance; + private BigDecimal duration; + + private BigDecimal co2; + + public BigDecimal getAirLineDistance() { + + return airLineDistance; + } + + + public void setAirLineDistance(BigDecimal airLineDistance) { + + this.airLineDistance = airLineDistance; + } + + + public BigDecimal getDistance() { + + return distance; + } + + + public void setDistance(BigDecimal distance) { + + this.distance = distance; + } + + + public BigDecimal getDieselDistance() { + + return dieselDistance; + } + + + public void setDieselDistance(BigDecimal dieselDistance) { + + this.dieselDistance = dieselDistance; + } + + + public BigDecimal getElectricDistance() { + + return electricDistance; + } + + + public void setElectricDistance(BigDecimal electricDistance) { + + this.electricDistance = electricDistance; + } + + + public BigDecimal getTollDistance() { + + return tollDistance; + } + + + public void setTollDistance(BigDecimal tollDistance) { + + this.tollDistance = tollDistance; + } + + + public BigDecimal getDuration() { + + return duration; + } + + + public void setDuration(BigDecimal duration) { + + this.duration = duration; + } + + + public BigDecimal getCo2() { + + return co2; + } + + + public void setCo2(BigDecimal co2) { + + this.co2 = co2; + } +} diff --git a/src/main/java/net/contargo/iris/route/RouteProduct.java b/src/main/java/net/contargo/iris/route/RouteProduct.java new file mode 100644 index 00000000..510e927b --- /dev/null +++ b/src/main/java/net/contargo/iris/route/RouteProduct.java @@ -0,0 +1,21 @@ +package net.contargo.iris.route; + +/** + * Represents a type of transport. It's either a oneway transport or a roundtrip transport. + * + * @author Aljona Murygina - murygina@synyx.de + */ +public enum RouteProduct { + + ONEWAY, + ROUNDTRIP; + + public static RouteProduct fromIsRoundtrip(boolean isRoundtrip) { + + if (isRoundtrip) { + return ROUNDTRIP; + } else { + return ONEWAY; + } + } +} diff --git a/src/main/java/net/contargo/iris/route/RouteType.java b/src/main/java/net/contargo/iris/route/RouteType.java new file mode 100644 index 00000000..c3a4c939 --- /dev/null +++ b/src/main/java/net/contargo/iris/route/RouteType.java @@ -0,0 +1,26 @@ +package net.contargo.iris.route; + +/** + * Enum to mark a {@Route}. + * + * @author Michael Herbold - herbold@synyx.de + * @author Aljona Murygina - murygina@synyx.de + */ +public enum RouteType { + + BARGE("route.type.barge"), + RAIL("route.type.rail"), + TRUCK("route.type.truck"); + + private String messageKey; + + private RouteType(String messageKey) { + + this.messageKey = messageKey; + } + + public String getMessageKey() { + + return messageKey; + } +} diff --git a/src/main/java/net/contargo/iris/route/TruckRouteParts.java b/src/main/java/net/contargo/iris/route/TruckRouteParts.java new file mode 100644 index 00000000..ba64c86e --- /dev/null +++ b/src/main/java/net/contargo/iris/route/TruckRouteParts.java @@ -0,0 +1,89 @@ +package net.contargo.iris.route; + +import net.contargo.iris.address.Address; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +/** + * Container Class for Routepartlist, containing only Truck-RouteParts. + * + * @author Arnold Franke - franke@synyx.de + */ +public class TruckRouteParts { + + private List truckRoutePartList = new ArrayList<>(); + + public TruckRouteParts(List truckRoutePartList) { + + for (RoutePart truckRoutePart : truckRoutePartList) { + if (!truckRoutePart.isOfType(RouteType.TRUCK)) { + throw new IllegalArgumentException("Can't add non-Truck Routepart to TruckRoutParts"); + } + } + + this.truckRoutePartList = truckRoutePartList; + } + + public List getTruckRoutePartList() { + + return Collections.unmodifiableList(truckRoutePartList); + } + + + public void setTruckRoutePartList(List truckRoutePartList) { + + this.truckRoutePartList = truckRoutePartList; + } + + + public void addTruckRoutePart(RoutePart truckRoutePart) { + + if (truckRoutePart.isOfType(RouteType.TRUCK)) { + truckRoutePartList.add(truckRoutePart); + } else { + throw new IllegalArgumentException("Can't add non-Truck Routepart to TruckRoutParts"); + } + } + + + public Address extractDestinationAddress() { + + for (RoutePart routePart : truckRoutePartList) { + if (routePart.getDestination() instanceof Address) { + return (Address) routePart.getDestination(); + } + } + + return null; + } + + + boolean isEqualRoundTrip() { + + if (truckRoutePartList.size() % 2 != 0) { + return false; + } + + for (int i = 0; i < truckRoutePartList.size(); i++) { + RoutePart routePart = truckRoutePartList.get(i); + RoutePart contraRoutePart = truckRoutePartList.get(truckRoutePartList.size() - 1 - i); + + if (!routePart.getOrigin().equals(contraRoutePart.getDestination())) { + return false; + } + } + + return true; + } + + + public void reduceToOneway() { + + if (isEqualRoundTrip()) { + truckRoutePartList.subList(truckRoutePartList.size() / 2, truckRoutePartList.size()).clear(); + } + } +} diff --git a/src/main/java/net/contargo/iris/routedatarevision/RouteDataRevision.java b/src/main/java/net/contargo/iris/routedatarevision/RouteDataRevision.java new file mode 100644 index 00000000..f23dcad2 --- /dev/null +++ b/src/main/java/net/contargo/iris/routedatarevision/RouteDataRevision.java @@ -0,0 +1,163 @@ +package net.contargo.iris.routedatarevision; + +import net.contargo.iris.terminal.Terminal; + +import net.contargo.validation.bigdecimal.BigDecimalValidate; + +import java.math.BigDecimal; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import javax.validation.constraints.NotNull; + + +/** + * Entity holding the information of route data revisions. + * + * @author Tobias Schneider - schneider@synyx.de + */ +@Entity +public class RouteDataRevision { + + private static final int DEG_180 = 180; + + private static final int MAX_DEC_15 = 15; + private static final int MAX_FRAC_2 = 2; + private static final int MAX_FRAC_10 = 10; + private static final int MAX_DEC_3 = 3; + private static final int MIN_0 = 0; + + @Id + @GeneratedValue + private Long id; + + @NotNull + @ManyToOne + private Terminal terminal; + + @NotNull + @BigDecimalValidate(maxFractionalPlaces = MAX_FRAC_2, maxDecimalPlaces = MAX_DEC_15) + private BigDecimal truckDistanceOneWay; + + @NotNull + @BigDecimalValidate(maxFractionalPlaces = MAX_FRAC_2, maxDecimalPlaces = MAX_DEC_15) + private BigDecimal tollDistanceOneWay; + + @NotNull + @BigDecimalValidate(maxFractionalPlaces = MAX_FRAC_2, maxDecimalPlaces = MAX_DEC_15) + private BigDecimal airlineDistance; + + @NotNull + @BigDecimalValidate( + minValue = -DEG_180, maxValue = DEG_180, maxFractionalPlaces = MAX_FRAC_10, maxDecimalPlaces = MAX_DEC_3 + ) + private BigDecimal latitude; + + @NotNull + @BigDecimalValidate( + minValue = -DEG_180, maxValue = DEG_180, maxFractionalPlaces = MAX_FRAC_10, maxDecimalPlaces = MAX_DEC_3 + ) + private BigDecimal longitude; + + @NotNull + @BigDecimalValidate(minValue = MIN_0, maxFractionalPlaces = MAX_FRAC_2, maxDecimalPlaces = MAX_DEC_15) + private BigDecimal radius; + + public Long getId() { + + return id; + } + + + public void setId(Long id) { + + this.id = id; + } + + + public Terminal getTerminal() { + + return terminal; + } + + + public void setTerminal(Terminal terminal) { + + this.terminal = terminal; + } + + + public BigDecimal getTruckDistanceOneWay() { + + return truckDistanceOneWay; + } + + + public void setTruckDistanceOneWay(BigDecimal truckDistanceOneWay) { + + this.truckDistanceOneWay = truckDistanceOneWay; + } + + + public BigDecimal getTollDistanceOneWay() { + + return tollDistanceOneWay; + } + + + public void setTollDistanceOneWay(BigDecimal tollDistanceOneWay) { + + this.tollDistanceOneWay = tollDistanceOneWay; + } + + + public BigDecimal getAirlineDistance() { + + return airlineDistance; + } + + + public void setAirlineDistance(BigDecimal airlineDistance) { + + this.airlineDistance = airlineDistance; + } + + + public BigDecimal getLatitude() { + + return latitude; + } + + + public void setLatitude(BigDecimal latitude) { + + this.latitude = latitude; + } + + + public BigDecimal getLongitude() { + + return longitude; + } + + + public void setLongitude(BigDecimal longitude) { + + this.longitude = longitude; + } + + + public BigDecimal getRadius() { + + return radius; + } + + + public void setRadius(BigDecimal radius) { + + this.radius = radius; + } +} diff --git a/src/main/java/net/contargo/iris/routedatarevision/persistence/RouteDataRevisionRepository.java b/src/main/java/net/contargo/iris/routedatarevision/persistence/RouteDataRevisionRepository.java new file mode 100644 index 00000000..fccaf25c --- /dev/null +++ b/src/main/java/net/contargo/iris/routedatarevision/persistence/RouteDataRevisionRepository.java @@ -0,0 +1,38 @@ +package net.contargo.iris.routedatarevision.persistence; + +import net.contargo.iris.routedatarevision.RouteDataRevision; +import net.contargo.iris.terminal.Terminal; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.query.Param; + +import java.math.BigDecimal; + + +/** + * Repository for {@link net.contargo.iris.routedatarevision.RouteDataRevision RouteDataRevision} instances. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public interface RouteDataRevisionRepository extends Repository { + + @Query( + value = "SELECT * " + + "FROM " + + " (" + + " SELECT *, " + + " (6371 * acos(cos(radians(:latitude)) * cos(radians(latitude)) " + + " * cos(radians(longitude) - radians(:longitude)) + sin(radians(:latitude)) " + + " * sin(radians(latitude)))) AS distance " + + " FROM RouteDataRevision " + + " WHERE terminal_id = :terminal " + + " ) as dis " + + "WHERE dis.distance <= dis.radius " + + "ORDER BY dis.distance ASC " + + "LIMIT 0, 1", nativeQuery = true + ) + RouteDataRevision findNearest(@Param("terminal") Terminal terminal, + @Param("latitude") BigDecimal latitude, + @Param("longitude") BigDecimal longitude); +} diff --git a/src/main/java/net/contargo/iris/routedatarevision/service/RouteDataRevisionService.java b/src/main/java/net/contargo/iris/routedatarevision/service/RouteDataRevisionService.java new file mode 100644 index 00000000..7facbfb1 --- /dev/null +++ b/src/main/java/net/contargo/iris/routedatarevision/service/RouteDataRevisionService.java @@ -0,0 +1,28 @@ +package net.contargo.iris.routedatarevision.service; + +import net.contargo.iris.address.Address; +import net.contargo.iris.routedatarevision.RouteDataRevision; +import net.contargo.iris.terminal.Terminal; + + +/** + * Service to a revision of the received information from a routing service. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public interface RouteDataRevisionService { + + /** + * Method to receive the correct {@link net.contargo.iris.routedatarevision.RouteDataRevision RouteDataRevision} + * with the shortest distance between the requested {@link Address} and the + * {@link net.contargo.iris.routedatarevision.RouteDataRevision RouteDataRevision} saved in the database. + * + * @param terminal on which the {@link net.contargo.iris.routedatarevision.RouteDataRevision RouteDataRevision} + * information are based on + * @param destination describes the destination {@link Address} on which the {@link RouteDataRevision} information + * are provided for + * + * @return the {@link RouteDataRevision} with the best fit for the given {@link Terminal} and {@link Address} + */ + RouteDataRevision getRouteDataRevision(Terminal terminal, Address destination); +} diff --git a/src/main/java/net/contargo/iris/routedatarevision/service/RouteDataRevisionServiceImpl.java b/src/main/java/net/contargo/iris/routedatarevision/service/RouteDataRevisionServiceImpl.java new file mode 100644 index 00000000..0def9f85 --- /dev/null +++ b/src/main/java/net/contargo/iris/routedatarevision/service/RouteDataRevisionServiceImpl.java @@ -0,0 +1,26 @@ +package net.contargo.iris.routedatarevision.service; + +import net.contargo.iris.address.Address; +import net.contargo.iris.routedatarevision.RouteDataRevision; +import net.contargo.iris.routedatarevision.persistence.RouteDataRevisionRepository; +import net.contargo.iris.terminal.Terminal; + +import org.springframework.transaction.annotation.Transactional; + + +public class RouteDataRevisionServiceImpl implements RouteDataRevisionService { + + private final RouteDataRevisionRepository routeDataRevisionRepository; + + public RouteDataRevisionServiceImpl(RouteDataRevisionRepository routeDataRevisionRepository) { + + this.routeDataRevisionRepository = routeDataRevisionRepository; + } + + @Override + @Transactional(readOnly = true) + public RouteDataRevision getRouteDataRevision(Terminal terminal, Address destination) { + + return routeDataRevisionRepository.findNearest(terminal, destination.getLatitude(), destination.getLongitude()); + } +} diff --git a/src/main/java/net/contargo/iris/seaport/Seaport.java b/src/main/java/net/contargo/iris/seaport/Seaport.java new file mode 100644 index 00000000..3dd4ad75 --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/Seaport.java @@ -0,0 +1,126 @@ +package net.contargo.iris.seaport; + +import net.contargo.iris.GeoLocation; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; + +import org.hibernate.validator.constraints.NotEmpty; + +import java.math.BigInteger; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import javax.validation.constraints.Size; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +@Entity(name = "Seaport") +public class Seaport extends GeoLocation { + + private static final int MAX_NAME_SIZE = 254; + + @Id + @GeneratedValue + private Long id; + + @NotEmpty + @Size(max = MAX_NAME_SIZE) + private String name; + + private boolean enabled = false; + + private BigInteger uniqueId; + + public Seaport() { + + // JPA entity classes must have a no-arg constructor + } + + + public Seaport(GeoLocation geoLocation) { + + this.setLatitude(geoLocation.getLatitude()); + this.setLongitude(geoLocation.getLongitude()); + } + + 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 boolean isEnabled() { + + return enabled; + } + + + public BigInteger getUniqueId() { + + return uniqueId; + } + + + public void setUniqueId(BigInteger uniqueId) { + + this.uniqueId = uniqueId; + } + + + @Override + public String getNiceName() { + + return getName(); + } + + + public void setEnabled(boolean enabled) { + + this.enabled = enabled; + } + + + @Override + public boolean equals(Object o) { + + return new EqualsBuilder().appendSuper(super.equals(o)).isEquals(); + } + + + @Override + public int hashCode() { + + return new HashCodeBuilder().appendSuper(super.hashCode()).toHashCode(); + } + + + @Override + public String toString() { + + return new ToStringBuilder(this).append("id", id).append("name", name).append("enabled", enabled).toString(); + } +} diff --git a/src/main/java/net/contargo/iris/seaport/api/SeaportApiController.java b/src/main/java/net/contargo/iris/seaport/api/SeaportApiController.java new file mode 100644 index 00000000..7173e63d --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/api/SeaportApiController.java @@ -0,0 +1,131 @@ +package net.contargo.iris.seaport.api; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; + +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.api.NotFoundException; +import net.contargo.iris.seaport.dto.SeaportDto; +import net.contargo.iris.seaport.dto.SeaportDtoService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import org.springframework.stereotype.Controller; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; + +import java.math.BigInteger; + +import java.util.HashSet; +import java.util.Set; + +import javax.validation.Valid; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + + +/** + * Public API controller that responds to seaport or terminal requests. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Oliver Messner - messner@synyx.de + * @author David Schilling - schilling@synyx.de + */ +@Api(value = "/seaports", description = "API endpoint to manage seaports.") +@Controller +@RequestMapping(value = "/seaports") +public class SeaportApiController extends AbstractController { + + private static final Logger LOG = LoggerFactory.getLogger(SeaportApiController.class); + + private final SeaportDtoService seaportDtoService; + + @Autowired + public SeaportApiController(SeaportDtoService seaportDtoService) { + + this.seaportDtoService = seaportDtoService; + } + + @ApiOperation(value = "Returns all active seaports.", notes = "Returns all active seaports.") + @RequestMapping(method = RequestMethod.GET) + @ResponseBody + public SeaportsResponse getSeaports() { + + SeaportsResponse response = new SeaportsResponse(); + + response.add(linkTo(methodOn(getClass()).getSeaports()).withSelfRel()); + + Set ports = new HashSet<>(); + ports.addAll(seaportDtoService.getAllActive()); + + response.setSeaports(ports); + + LOG.info("API: Returning {} active seaports ", ports.size()); + + return response; + } + + + @ApiOperation(value = "Returns the seaport for the given UID.", notes = "Returns the seaport for the given UID.") + @RequestMapping(value = SLASH + "{seaportuid}", method = RequestMethod.GET) + @ModelAttribute(RESPONSE) + public SeaportResponse getSeaportById(@PathVariable("seaportuid") BigInteger seaportUID) { + + SeaportResponse response = new SeaportResponse(); + + response.add(linkTo(methodOn(getClass()).getSeaportById(seaportUID)).withSelfRel()); + response.add(linkTo(methodOn(getClass()).getSeaports()).withRel("seaports")); + + SeaportDto port = seaportDtoService.getByUid(seaportUID); + + if (port == null) { + throw new NotFoundException("Cannot find Seaport for UID " + seaportUID); + } + + response.setSeaport(port); + + LOG.info("API: Returning seaport {}", port.getName()); + + return response; + } + + + @ApiOperation( + value = "Saves the given seaport with the specified UID.", + notes = "Saves the given seaport with the specified UID.", response = Void.class + ) + @RequestMapping(value = SLASH + "{seaportUid}", method = RequestMethod.PUT) + public ResponseEntity syncSeaport(@PathVariable("seaportUid") BigInteger seaportUID, + @Valid @RequestBody SeaportDto seaport) { + + boolean update = seaportDtoService.existsByUniqueId(seaportUID); + + if (update) { + seaportDtoService.updateSeaport(seaportUID, seaport); + + LOG.info("API: Updating seaport with unique id {}", seaportUID); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } else { + seaport.setUniqueId(seaportUID.toString()); + + seaportDtoService.save(seaport); + + LOG.info("API: Creating seaport with unique id {}", seaportUID); + + return new ResponseEntity(HttpStatus.CREATED); + } + } +} diff --git a/src/main/java/net/contargo/iris/seaport/api/SeaportResponse.java b/src/main/java/net/contargo/iris/seaport/api/SeaportResponse.java new file mode 100644 index 00000000..a8896bdc --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/api/SeaportResponse.java @@ -0,0 +1,27 @@ +package net.contargo.iris.seaport.api; + +import net.contargo.iris.seaport.dto.SeaportDto; + +import org.springframework.hateoas.ResourceSupport; + + +/** + * HATEOAS supporting response object for a single {@link net.contargo.iris.seaport.dto.SeaportDto}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +class SeaportResponse extends ResourceSupport { + + private SeaportDto seaport; + + public SeaportDto getSeaport() { + + return seaport; + } + + + public void setSeaport(SeaportDto seaport) { + + this.seaport = seaport; + } +} diff --git a/src/main/java/net/contargo/iris/seaport/api/SeaportsResponse.java b/src/main/java/net/contargo/iris/seaport/api/SeaportsResponse.java new file mode 100644 index 00000000..c4b9da4d --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/api/SeaportsResponse.java @@ -0,0 +1,29 @@ +package net.contargo.iris.seaport.api; + +import net.contargo.iris.seaport.dto.SeaportDto; + +import org.springframework.hateoas.ResourceSupport; + +import java.util.Set; + + +/** + * HATEOAS supporting response object for a list of {@link net.contargo.iris.seaport.dto.SeaportDto}s. + * + * @author Sandra Thieme - thieme@synyx.de + */ +class SeaportsResponse extends ResourceSupport { + + private Set seaports; + + public Set getSeaports() { + + return seaports; + } + + + public void setSeaports(Set seaports) { + + this.seaports = seaports; + } +} diff --git a/src/main/java/net/contargo/iris/seaport/dto/SeaportDto.java b/src/main/java/net/contargo/iris/seaport/dto/SeaportDto.java new file mode 100644 index 00000000..14ee651a --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/dto/SeaportDto.java @@ -0,0 +1,103 @@ +package net.contargo.iris.seaport.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import net.contargo.iris.address.dto.GeoLocationDto; +import net.contargo.iris.seaport.Seaport; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.constraints.Size; +import java.math.BigInteger; + + +/** + * @author Arnold Franke - franke@synyx.de + * @author Sandra Thieme - thieme@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public class SeaportDto extends GeoLocationDto { + + private static final int MAX_NAME_SIZE = 254; + private static final String SEAPORT = "SEAPORT"; + + @NotEmpty + @Size(max = MAX_NAME_SIZE) + private String name; + + private boolean enabled; + private String type; + + private String uniqueId; + + public SeaportDto() { + + // Used to create JSON Objects + } + + + public SeaportDto(Seaport seaport) { + + super(seaport); + + if (seaport != null) { + this.name = seaport.getName(); + this.enabled = seaport.isEnabled(); + this.uniqueId = seaport.getUniqueId() == null ? null : seaport.getUniqueId().toString(); + this.type = SEAPORT; + } + } + + public boolean isEnabled() { + + return enabled; + } + + + public String getName() { + + return name; + } + + + public void setEnabled(boolean enabled) { + + this.enabled = enabled; + } + + + public void setName(String name) { + + this.name = name; + } + + + @Override + public Seaport toEntity() { + + Seaport seaport = new Seaport(super.toEntity()); + seaport.setEnabled(this.enabled); + seaport.setName(this.name); + seaport.setUniqueId(this.uniqueId == null ? null : new BigInteger(this.uniqueId)); + + return seaport; + } + + + @Override + public String getType() { + + return type; + } + + + @JsonProperty("uniqueId") + public String getUniqueId() { + + return uniqueId; + } + + + public void setUniqueId(String uniqueId) { + + this.uniqueId = uniqueId; + } +} diff --git a/src/main/java/net/contargo/iris/seaport/dto/SeaportDtoService.java b/src/main/java/net/contargo/iris/seaport/dto/SeaportDtoService.java new file mode 100644 index 00000000..2c339b79 --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/dto/SeaportDtoService.java @@ -0,0 +1,37 @@ +package net.contargo.iris.seaport.dto; + +import java.math.BigInteger; + +import java.util.List; + + +/** + * View Bean Service for mapping of {@link net.contargo.iris.seaport.Seaport}s to {@link SeaportDto}s, Maps all return + * values of the methods of @link SeaportService} to View Beans. For detailed Javadoc see interface + * {@link net.contargo.iris.seaport.service.SeaportService}. + * + * @author Arnold Franke - franke@synyx.de + * @author Sandra Thieme - thieme@synyx.de + */ +public interface SeaportDtoService { + + List getAll(); + + + List getAllActive(); + + + SeaportDto getById(Long id); + + + SeaportDto save(SeaportDto seaPortDto); + + + boolean existsByUniqueId(BigInteger seaportUid); + + + SeaportDto updateSeaport(BigInteger seaportUid, SeaportDto seaportDto); + + + SeaportDto getByUid(BigInteger uid); +} diff --git a/src/main/java/net/contargo/iris/seaport/dto/SeaportDtoServiceImpl.java b/src/main/java/net/contargo/iris/seaport/dto/SeaportDtoServiceImpl.java new file mode 100644 index 00000000..bbde8659 --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/dto/SeaportDtoServiceImpl.java @@ -0,0 +1,95 @@ +package net.contargo.iris.seaport.dto; + +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.seaport.service.SeaportService; + +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author Arnold Franke - franke@synyx.de + * @author Sandra Thieme - thieme@synyx.de + */ +public class SeaportDtoServiceImpl implements SeaportDtoService { + + private final SeaportService seaportService; + + public SeaportDtoServiceImpl(SeaportService seaportService) { + + this.seaportService = seaportService; + } + + @Override + public List getAll() { + + List seaportDtos = new ArrayList<>(); + List seaports = seaportService.getAll(); + + for (Seaport seaport : seaports) { + seaportDtos.add(new SeaportDto(seaport)); + } + + return seaportDtos; + } + + + @Override + public List getAllActive() { + + List seaportDtos = new ArrayList<>(); + List seaports = seaportService.getAllActive(); + + for (Seaport seaport : seaports) { + seaportDtos.add(new SeaportDto(seaport)); + } + + return seaportDtos; + } + + + @Override + public SeaportDto getById(Long id) { + + Seaport seaport = seaportService.getById(id); + + return seaport == null ? null : new SeaportDto(seaport); + } + + + @Override + public SeaportDto save(SeaportDto seaPortDto) { + + Seaport seaportToSave = seaPortDto.toEntity(); + Seaport savedSeaport = seaportService.save(seaportToSave); + + return new SeaportDto(savedSeaport); + } + + + @Override + public boolean existsByUniqueId(BigInteger seaportUid) { + + return seaportService.existsByUniqueId(seaportUid); + } + + + @Override + public SeaportDto updateSeaport(BigInteger seaportUid, SeaportDto seaportDto) { + + Seaport seaport = seaportService.updateSeaport(seaportUid, seaportDto.toEntity()); + + return new SeaportDto(seaport); + } + + + @Override + public SeaportDto getByUid(BigInteger uid) { + + Seaport seaport = seaportService.getByUniqueId(uid); + + return seaport == null ? null : new SeaportDto(seaport); + } +} diff --git a/src/main/java/net/contargo/iris/seaport/persistence/SeaportRepository.java b/src/main/java/net/contargo/iris/seaport/persistence/SeaportRepository.java new file mode 100644 index 00000000..ad968f57 --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/persistence/SeaportRepository.java @@ -0,0 +1,37 @@ +package net.contargo.iris.seaport.persistence; + +import net.contargo.iris.seaport.Seaport; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import java.util.List; + + +/** + * Repository interface for {@link Seaport}. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +public interface SeaportRepository extends JpaRepository { + + List findByEnabled(boolean enabled); + + + Seaport findByLatitudeAndLongitude(BigDecimal latitude, BigDecimal longitude); + + + Seaport findByUniqueId(BigInteger uniqueId); + + + Seaport findByName(String name); + + + Seaport findByLatitudeAndLongitudeAndIdNot(BigDecimal latitude, BigDecimal longitude, Long terminalId); + + + Seaport findByNameAndIdNot(String name, Long terminalId); +} diff --git a/src/main/java/net/contargo/iris/seaport/service/NonUniqueSeaportException.java b/src/main/java/net/contargo/iris/seaport/service/NonUniqueSeaportException.java new file mode 100644 index 00000000..5b9e499d --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/service/NonUniqueSeaportException.java @@ -0,0 +1,25 @@ +package net.contargo.iris.seaport.service; + +import java.util.List; + +import static java.util.Arrays.asList; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +public class NonUniqueSeaportException extends IllegalArgumentException { + + private final List badFields; + + public NonUniqueSeaportException(String... badFields) { + + super("Seaport name and coordinates have to be unique."); + this.badFields = asList(badFields); + } + + public List getBadFields() { + + return badFields; + } +} diff --git a/src/main/java/net/contargo/iris/seaport/service/SeaportService.java b/src/main/java/net/contargo/iris/seaport/service/SeaportService.java new file mode 100644 index 00000000..032a358d --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/service/SeaportService.java @@ -0,0 +1,72 @@ +package net.contargo.iris.seaport.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.seaport.Seaport; + +import java.math.BigInteger; + +import java.util.List; + + +/** + * Provides services related to {@link Seaport} entities. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +public interface SeaportService { + + /** + * Returns all (active/inactive) {@link Seaport}s of the System. + * + * @return a List of all {@link Seaport}s + */ + List getAll(); + + + /** + * Returns all active {@link Seaport}s of the System. + * + * @return a List of active {@link Seaport}s + */ + List getAllActive(); + + + /** + * Returns the {@link Seaport} based on the given ID or null if not found. + * + * @param id the id to return a {@link Seaport} for + * + * @return the {@link Seaport} found or null + */ + Seaport getById(Long id); + + + Seaport getByUniqueId(BigInteger uniqueId); + + + /** + * Saves a {@link Seaport} to database.. + * + * @param seaport to save + * + * @return the persisted {@link Seaport} + */ + Seaport save(Seaport seaport); + + + /** + * Returns a {@link Seaport} by its latitude and longitude. + * + * @param location credentials to find {@link Seaport} with + * + * @return {@link Seaport} with given {@link GeoLocation} + */ + Seaport getByGeoLocation(GeoLocation location); + + + boolean existsByUniqueId(BigInteger seaportUid); + + + Seaport updateSeaport(BigInteger seaportUid, Seaport seaport); +} diff --git a/src/main/java/net/contargo/iris/seaport/service/SeaportServiceImpl.java b/src/main/java/net/contargo/iris/seaport/service/SeaportServiceImpl.java new file mode 100644 index 00000000..2e8d2c56 --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/service/SeaportServiceImpl.java @@ -0,0 +1,163 @@ +package net.contargo.iris.seaport.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.seaport.persistence.SeaportRepository; +import net.contargo.iris.sequence.service.SequenceService; + +import org.slf4j.Logger; + +import org.springframework.transaction.annotation.Transactional; + +import java.lang.invoke.MethodHandles; + +import java.math.BigInteger; + +import java.util.List; + +import javax.persistence.Entity; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Provides implementations related to {@link Seaport} entities. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@Transactional +public class SeaportServiceImpl implements SeaportService { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private final SeaportRepository seaportRepository; + private final SequenceService uniqueIdSequenceService; + + public SeaportServiceImpl(SeaportRepository seaportRepository, SequenceService uniqueIdSequenceService) { + + this.seaportRepository = seaportRepository; + this.uniqueIdSequenceService = uniqueIdSequenceService; + } + + @Override + public List getAll() { + + return seaportRepository.findAll(); + } + + + @Override + public List getAllActive() { + + return seaportRepository.findByEnabled(true); + } + + + @Override + public Seaport getById(Long id) { + + return seaportRepository.findOne(id); + } + + + @Override + public Seaport getByUniqueId(BigInteger uniqueId) { + + return seaportRepository.findByUniqueId(uniqueId); + } + + + @Override + public synchronized Seaport save(Seaport seaport) { + + if (seaport.getUniqueId() == null) { + seaport.setUniqueId(determineUniqueId()); + } + + checkUniqueConstraints(seaport); + + return seaportRepository.save(seaport); + } + + + @Override + public Seaport getByGeoLocation(GeoLocation location) { + + return seaportRepository.findByLatitudeAndLongitude(location.getLatitude(), location.getLongitude()); + } + + + @Override + public boolean existsByUniqueId(BigInteger seaportUid) { + + return getByUniqueId(seaportUid) != null; + } + + + @Override + public Seaport updateSeaport(BigInteger seaportUid, Seaport seaport) { + + Seaport savedSeaport = getByUniqueId(seaportUid); + savedSeaport.setEnabled(seaport.isEnabled()); + savedSeaport.setName(seaport.getName()); + savedSeaport.setLatitude(seaport.getLatitude()); + savedSeaport.setLongitude(seaport.getLongitude()); + + return save(savedSeaport); + } + + + @Transactional(readOnly = true) + void checkUniqueConstraints(Seaport seaport) { + + boolean uniqueCoordinates; + boolean uniqueName; + + if (seaport.getId() == null) { + // create + uniqueCoordinates = + seaportRepository.findByLatitudeAndLongitude(seaport.getLatitude(), seaport.getLongitude()) == null; + + uniqueName = seaportRepository.findByName(seaport.getName()) == null; + } else { + // update + Long seaportId = seaport.getId(); + + uniqueCoordinates = seaportRepository.findByLatitudeAndLongitudeAndIdNot(seaport.getLatitude(), + seaport.getLongitude(), seaportId) == null; + + uniqueName = seaportRepository.findByNameAndIdNot(seaport.getName(), seaportId) == null; + } + + if (!uniqueCoordinates) { + throw new NonUniqueSeaportException("latitude", "longitude"); + } + + if (!uniqueName) { + throw new NonUniqueSeaportException("name"); + } + } + + + BigInteger determineUniqueId() { + + String entityName = Seaport.class.getAnnotation(Entity.class).name(); + BigInteger nextUniqueId = uniqueIdSequenceService.getNextId(entityName); + + boolean isUniqueIdAlreadyAssigned = seaportRepository.findByUniqueId(nextUniqueId) != null; + + while (isUniqueIdAlreadyAssigned) { + // In this loop we increment the ID by ourselves to avoid write-accesses to the DB for performance. + LOG.warn("Terminal uniqueId {} already assigned - trying next uniqueId", nextUniqueId); + nextUniqueId = nextUniqueId.add(BigInteger.ONE); + + if (!(seaportRepository.findByUniqueId(nextUniqueId) != null)) { + isUniqueIdAlreadyAssigned = false; + uniqueIdSequenceService.setNextId(entityName, nextUniqueId); + } + } + + return nextUniqueId; + } +} diff --git a/src/main/java/net/contargo/iris/seaport/web/SeaportController.java b/src/main/java/net/contargo/iris/seaport/web/SeaportController.java new file mode 100644 index 00000000..4f49feaf --- /dev/null +++ b/src/main/java/net/contargo/iris/seaport/web/SeaportController.java @@ -0,0 +1,143 @@ +package net.contargo.iris.seaport.web; + +import net.contargo.iris.Message; +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.seaport.Seaport; +import net.contargo.iris.seaport.service.NonUniqueSeaportException; +import net.contargo.iris.seaport.service.SeaportService; +import net.contargo.iris.sequence.service.UniqueIdSequenceServiceException; + +import org.slf4j.Logger; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.ui.Model; + +import org.springframework.validation.BindingResult; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import java.lang.invoke.MethodHandles; + +import javax.validation.Valid; + +import static net.contargo.iris.api.AbstractController.SEAPORTS; +import static net.contargo.iris.api.AbstractController.SLASH; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Controller Class for all operations with {@link Seaport}s. + * + * @author Arnold Franke - franke@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@Controller +@RequestMapping(SLASH + SEAPORTS) +public class SeaportController extends AbstractController { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private static final String CONTROLLER_CONTEXT = "seaportManagement" + SLASH; + + private static final String SEAPORTS_ATTRIBUTE = SEAPORTS; + private static final String SEAPORT_ATTRIBUTE = "seaport"; + + private static final String SEAPORTS_VIEW = SEAPORTS; + private static final String SEAPORT_VIEW = "seaport"; + + private static final Message SAVE_SUCCESS_MESSAGE = Message.success("seaport.success.save.message"); + private static final Message UPDATE_SUCCESS_MESSAGE = Message.success("seaport.success.update.message"); + + private final SeaportService seaportService; + + @Autowired + public SeaportController(SeaportService seaportService) { + + this.seaportService = seaportService; + } + + @RequestMapping(method = RequestMethod.GET) + public String getAllSeaports(Model model) { + + model.addAttribute(SEAPORTS_ATTRIBUTE, seaportService.getAll()); + + return CONTROLLER_CONTEXT + SEAPORTS_VIEW; + } + + + @RequestMapping(value = SLASH + "new", method = RequestMethod.GET) + public String prepareForCreate(Model model) { + + model.addAttribute(SEAPORT_ATTRIBUTE, new Seaport()); + + return CONTROLLER_CONTEXT + SEAPORT_VIEW; + } + + + @RequestMapping(value = SLASH, method = RequestMethod.POST) + public String saveSeaport(@Valid @ModelAttribute Seaport seaport, BindingResult result, Model model, + RedirectAttributes redirectAttributes) { + + return saveOrUpdateSeaport(model, result, seaport, redirectAttributes, SAVE_SUCCESS_MESSAGE); + } + + + @RequestMapping(value = SLASH + ID_PARAM, method = RequestMethod.GET) + public String getSeaport(@PathVariable Long id, Model model) { + + model.addAttribute(SEAPORT_ATTRIBUTE, seaportService.getById(id)); + + return CONTROLLER_CONTEXT + SEAPORT_VIEW; + } + + + @RequestMapping(value = SLASH + ID_PARAM, method = RequestMethod.PUT) + public String updateSeaport(@Valid @ModelAttribute Seaport seaport, BindingResult result, @PathVariable Long id, + Model model, RedirectAttributes redirectAttributes) { + + seaport.setId(seaportService.getById(id).getId()); + + return saveOrUpdateSeaport(model, result, seaport, redirectAttributes, UPDATE_SUCCESS_MESSAGE); + } + + + private String saveOrUpdateSeaport(Model model, BindingResult result, Seaport seaport, + RedirectAttributes redirectAttributes, Message successMessage) { + + if (result.hasErrors()) { + model.addAttribute(SEAPORT_ATTRIBUTE, seaport); + + return CONTROLLER_CONTEXT + SEAPORT_VIEW; + } + + try { + Long id = seaportService.save(seaport).getId(); + + redirectAttributes.addFlashAttribute(MESSAGE, successMessage); + + return REDIRECT + WEBAPI_ROOT_URL + SEAPORTS + SLASH + id; + } catch (NonUniqueSeaportException e) { + for (String fieldName : e.getBadFields()) { + result.rejectValue(fieldName, "seaport.nonunique." + fieldName); + } + + model.addAttribute(SEAPORT_ATTRIBUTE, seaport); + + return CONTROLLER_CONTEXT + SEAPORT_VIEW; + } catch (UniqueIdSequenceServiceException e) { + model.addAttribute(SEAPORT_ATTRIBUTE, seaport); + model.addAttribute(AbstractController.MESSAGE, UNIQUEID_ERROR_MESSAGE); + LOG.error(e.getMessage()); + + return CONTROLLER_CONTEXT + SEAPORT_VIEW; + } + } +} diff --git a/src/main/java/net/contargo/iris/security/SpringUserAuthenticationService.java b/src/main/java/net/contargo/iris/security/SpringUserAuthenticationService.java new file mode 100644 index 00000000..2a66fcd8 --- /dev/null +++ b/src/main/java/net/contargo/iris/security/SpringUserAuthenticationService.java @@ -0,0 +1,26 @@ +package net.contargo.iris.security; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + + +/** + * Implementation of {@link UserAuthenticationService} which access {@link SecurityContextHolder}. + * + * @author David Schilling - schilling@synyx.de + */ +public class SpringUserAuthenticationService implements UserAuthenticationService { + + @Override + public Authentication getCurrentUser() { + + SecurityContext context = SecurityContextHolder.getContext(); + + if (context == null) { + return null; + } + + return context.getAuthentication(); + } +} diff --git a/src/main/java/net/contargo/iris/security/UserAuthenticationService.java b/src/main/java/net/contargo/iris/security/UserAuthenticationService.java new file mode 100644 index 00000000..763c6b94 --- /dev/null +++ b/src/main/java/net/contargo/iris/security/UserAuthenticationService.java @@ -0,0 +1,17 @@ +package net.contargo.iris.security; + +import org.springframework.security.core.Authentication; + + +/** + * Service to get information about the current User. + * + * @author David Schilling - schilling@synyx.de + */ +public interface UserAuthenticationService { + + /** + * @return the current user as {@link org.springframework.security.core.Authentication} + */ + Authentication getCurrentUser(); +} diff --git a/src/main/java/net/contargo/iris/sequence/UniqueIdSequence.java b/src/main/java/net/contargo/iris/sequence/UniqueIdSequence.java new file mode 100644 index 00000000..d9dfe885 --- /dev/null +++ b/src/main/java/net/contargo/iris/sequence/UniqueIdSequence.java @@ -0,0 +1,72 @@ +package net.contargo.iris.sequence; + +import org.hibernate.validator.constraints.NotEmpty; + +import java.math.BigInteger; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + + +/** + * Represents the next id to assign to an entity. + * + * @author Arnold Franke - franke@synyx.de + */ +@Entity +public class UniqueIdSequence { + + public static final String SYSTEM_UNIQUEID_PREFIX = "1301"; + public static final int SYSTEM_UNIQUEID_LENGTH = 16; + + @Id + @GeneratedValue + private Long id; + + @NotEmpty + private String entityName; + + private BigInteger nextId; + + UniqueIdSequence() { + + // JPA Entity classes must have a default constructor. + } + + + public UniqueIdSequence(String entityName, BigInteger nextId) { + + this.entityName = entityName; + this.nextId = nextId; + } + + public Long getId() { + + return id; + } + + + public String getEntityName() { + + return entityName; + } + + + public BigInteger getNextId() { + + return nextId; + } + + + public void incrementNextId() { + + nextId = nextId.add(BigInteger.ONE); + } + + + public void setNextId(BigInteger nextId) { + + this.nextId = nextId; + } +} diff --git a/src/main/java/net/contargo/iris/sequence/persistence/UniqueIdSequenceRepository.java b/src/main/java/net/contargo/iris/sequence/persistence/UniqueIdSequenceRepository.java new file mode 100644 index 00000000..f5cbfb4a --- /dev/null +++ b/src/main/java/net/contargo/iris/sequence/persistence/UniqueIdSequenceRepository.java @@ -0,0 +1,14 @@ +package net.contargo.iris.sequence.persistence; + +import net.contargo.iris.sequence.UniqueIdSequence; + +import org.springframework.data.jpa.repository.JpaRepository; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public interface UniqueIdSequenceRepository extends JpaRepository { + + UniqueIdSequence findByEntityName(String entityName); +} diff --git a/src/main/java/net/contargo/iris/sequence/service/SequenceService.java b/src/main/java/net/contargo/iris/sequence/service/SequenceService.java new file mode 100644 index 00000000..2a8840ff --- /dev/null +++ b/src/main/java/net/contargo/iris/sequence/service/SequenceService.java @@ -0,0 +1,30 @@ +package net.contargo.iris.sequence.service; + +import java.math.BigInteger; + + +/** + * Manages Sequences of IDs in the database. + * + * @author Arnold Franke - franke@synyx.de + */ +public interface SequenceService { + + /** + * Determines the next id to use for the Entity of the given entity name and increments the Id in the DB. + * + * @param entityName as selector to search next id of this entity + * + * @return the next id of the given entity + */ + BigInteger getNextId(String entityName); + + + /** + * Set the next id to use manually. + * + * @param entityName to set the id + * @param id to set + */ + void setNextId(String entityName, BigInteger id); +} diff --git a/src/main/java/net/contargo/iris/sequence/service/UniqueIdSequenceServiceException.java b/src/main/java/net/contargo/iris/sequence/service/UniqueIdSequenceServiceException.java new file mode 100644 index 00000000..2b09ae79 --- /dev/null +++ b/src/main/java/net/contargo/iris/sequence/service/UniqueIdSequenceServiceException.java @@ -0,0 +1,12 @@ +package net.contargo.iris.sequence.service; + +/** + * @author Arnold Franke - franke@synyx.de + */ +public class UniqueIdSequenceServiceException extends RuntimeException { + + public UniqueIdSequenceServiceException(String message) { + + super(message); + } +} diff --git a/src/main/java/net/contargo/iris/sequence/service/UniqueIdSequenceServiceImpl.java b/src/main/java/net/contargo/iris/sequence/service/UniqueIdSequenceServiceImpl.java new file mode 100644 index 00000000..183d4a95 --- /dev/null +++ b/src/main/java/net/contargo/iris/sequence/service/UniqueIdSequenceServiceImpl.java @@ -0,0 +1,72 @@ +package net.contargo.iris.sequence.service; + +import net.contargo.iris.sequence.UniqueIdSequence; +import net.contargo.iris.sequence.persistence.UniqueIdSequenceRepository; + +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigInteger; + + +/** + * Manages sequences of Unique IDs in the UniqueIdSequence table. + * + * @author Arnold Franke - franke@synyx.de + */ +@Transactional +public class UniqueIdSequenceServiceImpl implements SequenceService { + + private final UniqueIdSequenceRepository uniqueIdSequenceRepository; + + public UniqueIdSequenceServiceImpl(UniqueIdSequenceRepository uniqueIdSequenceRepository) { + + this.uniqueIdSequenceRepository = uniqueIdSequenceRepository; + } + + @Override + public synchronized BigInteger getNextId(String entityName) { + + UniqueIdSequence uniqueIdSequence = uniqueIdSequenceRepository.findByEntityName(entityName); + + if (uniqueIdSequence == null) { + throw new UniqueIdSequenceServiceException("No uniqueId sequence found in the database."); + } + + BigInteger nextUniqueId = uniqueIdSequence.getNextId(); + + incrementNextId(uniqueIdSequence); + + return nextUniqueId; + } + + + private void incrementNextId(UniqueIdSequence uniqueIdSequence) { + + uniqueIdSequence.incrementNextId(); + + checkValidity(uniqueIdSequence.getNextId()); + + uniqueIdSequenceRepository.save(uniqueIdSequence); + } + + + void checkValidity(BigInteger nextId) { + + if (!nextId.toString().startsWith(UniqueIdSequence.SYSTEM_UNIQUEID_PREFIX) + || nextId.toString().length() != UniqueIdSequence.SYSTEM_UNIQUEID_LENGTH) { + throw new UniqueIdSequenceServiceException( + "The automatically generated uniqueId is not valid for this system. It has to be 16 digits long and " + + "has to start with the system identifier " + UniqueIdSequence.SYSTEM_UNIQUEID_PREFIX); + } + } + + + @Override + public void setNextId(String entityName, BigInteger id) { + + UniqueIdSequence uniqueIdSequence = uniqueIdSequenceRepository.findByEntityName(entityName); + uniqueIdSequence.setNextId(id); + checkValidity(uniqueIdSequence.getNextId()); + uniqueIdSequenceRepository.save(uniqueIdSequence); + } +} diff --git a/src/main/java/net/contargo/iris/startup/IrisStartupWatcher.java b/src/main/java/net/contargo/iris/startup/IrisStartupWatcher.java new file mode 100644 index 00000000..5d6b3a4a --- /dev/null +++ b/src/main/java/net/contargo/iris/startup/IrisStartupWatcher.java @@ -0,0 +1,36 @@ +package net.contargo.iris.startup; + +import net.contargo.iris.address.staticsearch.service.StaticAddressService; + +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; + + +/** + * Looks for event {@link ContextRefreshedEvent}, starts to do tasks that have to be done on application start. + * + * @author Michael Herbold - herbold@synyx.de + */ +public class IrisStartupWatcher implements ApplicationListener { + + private final StaticAddressService staticAddressService; + + private boolean runOnce; + + public IrisStartupWatcher(StaticAddressService staticAddressService) { + + this.staticAddressService = staticAddressService; + runOnce = false; + } + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + + if (!runOnce) { + // added this boolean, because ContextRefreshedEvent is fired currently three times, + // because this event is fired for each context file separately (applicationContext/web/rest) + staticAddressService.fillMissingHashKeys(); + runOnce = true; + } + } +} diff --git a/src/main/java/net/contargo/iris/terminal/Region.java b/src/main/java/net/contargo/iris/terminal/Region.java new file mode 100644 index 00000000..44af9236 --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/Region.java @@ -0,0 +1,25 @@ +package net.contargo.iris.terminal; + +/** + * @author Aljona Murygina - murygina@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public enum Region { + + NIEDERRHEIN("region.niederrhein"), + OBERRHEIN("region.oberrhein"), + SCHELDE("region.schelde"), + NOT_SET("region.no"); + + private final String messageKey; + + private Region(String messageKey) { + + this.messageKey = messageKey; + } + + public String getMessageKey() { + + return messageKey; + } +} diff --git a/src/main/java/net/contargo/iris/terminal/Terminal.java b/src/main/java/net/contargo/iris/terminal/Terminal.java new file mode 100644 index 00000000..a2faa96f --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/Terminal.java @@ -0,0 +1,145 @@ +package net.contargo.iris.terminal; + +import net.contargo.iris.GeoLocation; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; + +import org.hibernate.validator.constraints.NotEmpty; + +import java.math.BigInteger; + +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import javax.validation.constraints.Size; + + +/** + * Represents a terminal. + * + * @author Sandra Thieme - thieme@synyx.de + */ +@Entity(name = "Terminal") +public class Terminal extends GeoLocation { + + private static final int MAX_NAME_SIZE = 254; + + @Id + @GeneratedValue + private Long id; + + @NotEmpty + @Size(max = MAX_NAME_SIZE) + private String name; + + private boolean enabled = false; + + @Enumerated(EnumType.STRING) + private Region region = Region.NOT_SET; + + private BigInteger uniqueId; + + public Terminal() { + + // JPA entity classes must have a no-arg constructor + } + + + public Terminal(GeoLocation geoLocation) { + + this.setLatitude(geoLocation.getLatitude()); + this.setLongitude(geoLocation.getLongitude()); + } + + public Long getId() { + + return id; + } + + + public void setId(Long id) { + + this.id = id; + } + + + public boolean isEnabled() { + + return enabled; + } + + + public void setEnabled(boolean enabled) { + + this.enabled = enabled; + } + + + public String getName() { + + return name; + } + + + public void setName(String name) { + + this.name = name; + } + + + public BigInteger getUniqueId() { + + return uniqueId; + } + + + public void setUniqueId(BigInteger uniqueId) { + + this.uniqueId = uniqueId; + } + + + public Region getRegion() { + + return region; + } + + + public void setRegion(Region region) { + + this.region = region; + } + + + @Override + public String getNiceName() { + + return getName(); + } + + + @Override + public boolean equals(Object o) { + + return new EqualsBuilder().appendSuper(super.equals(o)).isEquals(); + } + + + @Override + public int hashCode() { + + return new HashCodeBuilder().appendSuper(super.hashCode()).toHashCode(); + } + + + @Override + public String toString() { + + return new ToStringBuilder(this).append("id", id).append("name", name).append("enabled", enabled).toString(); + } +} diff --git a/src/main/java/net/contargo/iris/terminal/api/TerminalApiController.java b/src/main/java/net/contargo/iris/terminal/api/TerminalApiController.java new file mode 100644 index 00000000..fbcd6cf4 --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/api/TerminalApiController.java @@ -0,0 +1,176 @@ +package net.contargo.iris.terminal.api; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; + +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.api.NotFoundException; +import net.contargo.iris.connection.dto.SeaportTerminalConnectionDtoService; +import net.contargo.iris.route.RouteType; +import net.contargo.iris.seaport.dto.SeaportDto; +import net.contargo.iris.seaport.dto.SeaportDtoService; +import net.contargo.iris.terminal.dto.TerminalDto; +import net.contargo.iris.terminal.dto.TerminalDtoService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import org.springframework.stereotype.Controller; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import java.math.BigInteger; + +import java.util.Collections; +import java.util.List; + +import javax.validation.Valid; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + + +/** + * @author Sandra Thieme - thieme@synyx.de + * @author Oliver Messner - messner@synyx.de + * @author David Schilling - schilling@synyx.de + */ +@Api(value = "/terminals", description = "API endpoint to manage terminal.") +@Controller +@RequestMapping(value = "/terminals") +public class TerminalApiController extends AbstractController { + + private static final Logger LOG = LoggerFactory.getLogger(TerminalApiController.class); + + private final TerminalDtoService terminalDtoService; + private final SeaportDtoService seaportDtoService; + private final SeaportTerminalConnectionDtoService seaportTerminalConnectionDtoService; + + @Autowired + public TerminalApiController(TerminalDtoService terminalDtoService, SeaportDtoService seaportDtoService, + SeaportTerminalConnectionDtoService seaportTerminalConnectionDtoService) { + + this.terminalDtoService = terminalDtoService; + this.seaportDtoService = seaportDtoService; + this.seaportTerminalConnectionDtoService = seaportTerminalConnectionDtoService; + } + + @ApiOperation(value = "Returns all active terminals.", notes = "Returns all active terminals.") + @RequestMapping(method = RequestMethod.GET) + @ModelAttribute(RESPONSE) + public TerminalsResponse getTerminals() { + + TerminalsResponse response = new TerminalsResponse(); + + response.add(linkTo(methodOn(getClass()).getTerminals()).withSelfRel()); + + List allActive = terminalDtoService.getAllActive(); + response.setTerminals(allActive); + + LOG.info("API: Returning {} active terminals", allActive.size()); + + return response; + } + + + @ApiOperation( + value = + "Returns all terminals that are part of a connection with the given routetype and the specified seaport.", + notes = + "Returns all terminals that are part of a connection with the given routetype and the specified seaport." + ) + @RequestMapping(params = { "seaportUid", }, method = RequestMethod.GET) + @ModelAttribute(RESPONSE) + public TerminalsResponse getTerminalsForSeaportAndRouteType( + @RequestParam(value = "seaportUid") BigInteger seaportUID, + @RequestParam(value = "routeType") RouteType routeType) { + + TerminalsResponse response = new TerminalsResponse(); + + response.add(linkTo(methodOn(getClass()).getTerminalsForSeaportAndRouteType(seaportUID, routeType)) + .withSelfRel()); + + SeaportDto seaportDto = seaportDtoService.getByUid(seaportUID); + + if (seaportDto == null) { + response.setTerminals(Collections.emptyList()); + + LOG.info("Cannnot find Seaport for UID {}", seaportUID); + } else { + List terminals = + seaportTerminalConnectionDtoService.findTerminalsConnectedToSeaPortByRouteType(seaportDto, routeType); + + response.setTerminals(terminals); + + LOG.info("API: Returning {} terminals connected to seaport {} via {}", terminals.size(), + seaportDto.getName(), routeType); + } + + return response; + } + + + @ApiOperation( + value = "Return the terminal with the given terminalUID.", + notes = "Return the terminal with the given terminalUID." + ) + @RequestMapping(value = SLASH + "{terminalUid}", method = RequestMethod.GET) + @ModelAttribute(RESPONSE) + public TerminalResponse getTerminalByUid(@PathVariable("terminalUid") BigInteger uid) { + + TerminalResponse response = new TerminalResponse(); + + response.add(linkTo(methodOn(getClass()).getTerminalByUid(uid)).withSelfRel()); + response.add(linkTo(methodOn(getClass()).getTerminals()).withRel("terminals")); + + TerminalDto t = terminalDtoService.getByUid(uid); + + if (t == null) { + throw new NotFoundException("Cannot find Terminal for UID " + uid); + } + + response.setTerminal(t); + + LOG.info("API: Returning terminal {}", t.getName()); + + return response; + } + + + @ApiOperation( + value = "Saves the terminal with the given terminalUID.", + notes = "Saves the terminal with the given terminalUID.", response = Void.class + ) + @RequestMapping(value = SLASH + "{terminalUid}", method = RequestMethod.PUT) + public ResponseEntity syncTerminal(@PathVariable("terminalUid") BigInteger terminalUid, + @Valid @RequestBody TerminalDto terminalDto) { + + boolean update = terminalDtoService.existsByUniqueId(terminalUid); + + if (update) { + terminalDtoService.updateTerminal(terminalUid, terminalDto); + + LOG.info("API: Updating terminal with unique id {}", terminalUid); + + return new ResponseEntity(HttpStatus.NO_CONTENT); + } else { + terminalDto.setUniqueId(terminalUid.toString()); + + terminalDtoService.save(terminalDto); + + LOG.info("API: Creating terminal with unique id {}", terminalUid); + + return new ResponseEntity(HttpStatus.CREATED); + } + } +} diff --git a/src/main/java/net/contargo/iris/terminal/api/TerminalResponse.java b/src/main/java/net/contargo/iris/terminal/api/TerminalResponse.java new file mode 100644 index 00000000..4a114b71 --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/api/TerminalResponse.java @@ -0,0 +1,27 @@ +package net.contargo.iris.terminal.api; + +import net.contargo.iris.terminal.dto.TerminalDto; + +import org.springframework.hateoas.ResourceSupport; + + +/** + * HATEOAS supporting response object for a single {@link net.contargo.iris.terminal.dto.TerminalDto}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +class TerminalResponse extends ResourceSupport { + + private TerminalDto terminal; + + public TerminalDto getTerminal() { + + return terminal; + } + + + public void setTerminal(TerminalDto terminal) { + + this.terminal = terminal; + } +} diff --git a/src/main/java/net/contargo/iris/terminal/api/TerminalsResponse.java b/src/main/java/net/contargo/iris/terminal/api/TerminalsResponse.java new file mode 100644 index 00000000..348d36f9 --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/api/TerminalsResponse.java @@ -0,0 +1,29 @@ +package net.contargo.iris.terminal.api; + +import net.contargo.iris.terminal.dto.TerminalDto; + +import org.springframework.hateoas.ResourceSupport; + +import java.util.List; + + +/** + * HATEOAS supporting response object for a list of {@link net.contargo.iris.terminal.dto.TerminalDto}s. + * + * @author Sandra Thieme - thieme@synyx.de + */ +class TerminalsResponse extends ResourceSupport { + + private List terminals; + + public List getTerminals() { + + return terminals; + } + + + public void setTerminals(List terminals) { + + this.terminals = terminals; + } +} diff --git a/src/main/java/net/contargo/iris/terminal/dto/TerminalDto.java b/src/main/java/net/contargo/iris/terminal/dto/TerminalDto.java new file mode 100644 index 00000000..5e48bef7 --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/dto/TerminalDto.java @@ -0,0 +1,124 @@ +package net.contargo.iris.terminal.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import net.contargo.iris.address.dto.GeoLocationDto; +import net.contargo.iris.terminal.Region; +import net.contargo.iris.terminal.Terminal; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.constraints.Size; +import java.math.BigInteger; + + +/** + * View Bean Class for Terminal. + * + * @author Arnold Franke - franke@synyx.de + * @author Oliver Messner - messner@synyx.de + */ +public final class TerminalDto extends GeoLocationDto { + + private static final int MAX_NAME_SIZE = 254; + private static final String TERMINAL = "TERMINAL"; + + @NotEmpty + @Size(max = MAX_NAME_SIZE) + private String name; + + private boolean enabled; + + private String uniqueId; + private String type; + private Region region; + + public TerminalDto() { + + // Used to create JSON Objects + } + + + public TerminalDto(Terminal terminal) { + + super(terminal); + + if (terminal != null) { + this.name = terminal.getName(); + this.enabled = terminal.isEnabled(); + this.type = TERMINAL; + this.uniqueId = terminal.getUniqueId() == null ? null : terminal.getUniqueId().toString(); + + if (terminal.getRegion() != null) { + this.region = terminal.getRegion(); + } + } + } + + @JsonProperty("uniqueId") + public String getUniqueId() { + + return uniqueId; + } + + + public void setUniqueId(String uniqueId) { + + this.uniqueId = uniqueId; + } + + + // Setters are needed so this DTO can be used as @ModelAttribute in Spring MVC + public void setName(String name) { + + this.name = name; + } + + + public void setEnabled(boolean enabled) { + + this.enabled = enabled; + } + + + public boolean isEnabled() { + + return enabled; + } + + + public String getName() { + + return name; + } + + + public Region getRegion() { + + return region; + } + + + public void setRegion(Region region) { + + this.region = region; + } + + + @Override + public Terminal toEntity() { + + Terminal terminal = new Terminal(super.toEntity()); + terminal.setEnabled(this.enabled); + terminal.setName(this.name); + terminal.setUniqueId(this.uniqueId == null ? null : new BigInteger(this.uniqueId)); + terminal.setRegion(this.region); + + return terminal; + } + + + @Override + public String getType() { + + return type; + } +} diff --git a/src/main/java/net/contargo/iris/terminal/dto/TerminalDtoService.java b/src/main/java/net/contargo/iris/terminal/dto/TerminalDtoService.java new file mode 100644 index 00000000..fe2e396e --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/dto/TerminalDtoService.java @@ -0,0 +1,66 @@ +package net.contargo.iris.terminal.dto; + +import java.math.BigInteger; + +import java.util.List; + + +/** + * View Bean Service for mapping of {@link net.contargo.iris.terminal.Terminal}s to {@link TerminalDto}s, Maps all + * return values of the methods of TerminalService to View Beans. For detailed Javadoc see interface + * {@link net.contargo.iris.terminal.service.TerminalService} + * + * @author Arnold Franke - franke@synyx.de + * @author Sandra Thieme - thieme@synyx.de + */ +public interface TerminalDtoService { + + /** + * @return a List of all Terminals + */ + List getAll(); + + + /** + * @return a List of alle enabled Terminals. + */ + List getAllActive(); + + + /** + * Save the Terminal. + * + * @param terminalDto + * + * @return The terminalDto constructed from the saved Entity. + */ + TerminalDto save(TerminalDto terminalDto); + + + /** + * @param uid the {@link net.contargo.iris.terminal.Terminal}'s uniqueId + * + * @return The Terminal found to the given id. Null if no terminal is found. + */ + TerminalDto getByUid(BigInteger uid); + + + /** + * Checks whether a {@link net.contargo.iris.terminal.Terminal} exists, + * + * @param uniqueId the terminal's uniqueId + * + * @return true if the terminal exists, else false + */ + boolean existsByUniqueId(BigInteger uniqueId); + + + /** + * Updates the {@link net.contargo.iris.terminal.Terminal} specified by its uniqueId with the values given in + * {@code terminalDto}. + * + * @param terminalUid the uniqueId of the terminal that should be updated + * @param terminalDto the dto providing values that should be updated + */ + TerminalDto updateTerminal(BigInteger terminalUid, TerminalDto terminalDto); +} diff --git a/src/main/java/net/contargo/iris/terminal/dto/TerminalDtoServiceImpl.java b/src/main/java/net/contargo/iris/terminal/dto/TerminalDtoServiceImpl.java new file mode 100644 index 00000000..9c666e6e --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/dto/TerminalDtoServiceImpl.java @@ -0,0 +1,86 @@ +package net.contargo.iris.terminal.dto; + +import net.contargo.iris.terminal.Terminal; +import net.contargo.iris.terminal.service.TerminalService; + +import java.math.BigInteger; + +import java.util.ArrayList; +import java.util.List; + + +/** + * @author Arnold Franke - franke@synyx.de + * @author Sandra Thieme - thieme@synyx.de + */ +public class TerminalDtoServiceImpl implements TerminalDtoService { + + private final TerminalService terminalService; + + public TerminalDtoServiceImpl(TerminalService terminalService) { + + this.terminalService = terminalService; + } + + @Override + public List getAll() { + + List termialDtos = new ArrayList<>(); + List terminals = terminalService.getAll(); + + for (Terminal terminal : terminals) { + termialDtos.add(new TerminalDto(terminal)); + } + + return termialDtos; + } + + + @Override + public List getAllActive() { + + List termialDtos = new ArrayList<>(); + List terminals = terminalService.getAllActive(); + + for (Terminal terminal : terminals) { + termialDtos.add(new TerminalDto(terminal)); + } + + return termialDtos; + } + + + @Override + public TerminalDto save(TerminalDto terminalDto) { + + Terminal terminalToSave = terminalDto.toEntity(); + Terminal savedTerminal = terminalService.save(terminalToSave); + + return new TerminalDto(savedTerminal); + } + + + @Override + public TerminalDto getByUid(BigInteger uid) { + + Terminal terminal = terminalService.getByUniqueId(uid); + + return terminal == null ? null : new TerminalDto(terminal); + } + + + @Override + public boolean existsByUniqueId(BigInteger uniqueId) { + + return terminalService.existsByUniqueId(uniqueId); + } + + + @Override + public TerminalDto updateTerminal(BigInteger terminalUid, TerminalDto terminalDto) { + + Terminal terminal = terminalService.updateTerminal(terminalUid, terminalDto.toEntity()); + + return new TerminalDto(terminal); + } +} diff --git a/src/main/java/net/contargo/iris/terminal/persistence/TerminalRepository.java b/src/main/java/net/contargo/iris/terminal/persistence/TerminalRepository.java new file mode 100644 index 00000000..679373bb --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/persistence/TerminalRepository.java @@ -0,0 +1,36 @@ +package net.contargo.iris.terminal.persistence; + +import net.contargo.iris.terminal.Terminal; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import java.util.List; + + +/** + * Repository interface for {@link Terminal}s. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public interface TerminalRepository extends JpaRepository { + + List findByEnabled(boolean enabled); + + + Terminal findByLatitudeAndLongitude(BigDecimal latitude, BigDecimal longitude); + + + Terminal findByUniqueId(BigInteger uniqueId); + + + Terminal findByName(String name); + + + Terminal findByLatitudeAndLongitudeAndIdNot(BigDecimal latitude, BigDecimal longitude, Long terminalId); + + + Terminal findByNameAndIdNot(String name, Long terminalId); +} diff --git a/src/main/java/net/contargo/iris/terminal/service/NonUniqueTerminalException.java b/src/main/java/net/contargo/iris/terminal/service/NonUniqueTerminalException.java new file mode 100644 index 00000000..e4b423bd --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/service/NonUniqueTerminalException.java @@ -0,0 +1,25 @@ +package net.contargo.iris.terminal.service; + +import java.util.List; + +import static java.util.Arrays.asList; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +public class NonUniqueTerminalException extends IllegalArgumentException { + + private final List badFields; + + public NonUniqueTerminalException(String... badFields) { + + super("Terminal name and coordinates have to be unique."); + this.badFields = asList(badFields); + } + + public List getBadFields() { + + return badFields; + } +} diff --git a/src/main/java/net/contargo/iris/terminal/service/TerminalService.java b/src/main/java/net/contargo/iris/terminal/service/TerminalService.java new file mode 100644 index 00000000..7eead494 --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/service/TerminalService.java @@ -0,0 +1,93 @@ +package net.contargo.iris.terminal.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.terminal.Terminal; + +import java.math.BigInteger; + +import java.util.List; + + +/** + * Provides services related to {@link Terminal} entities. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +public interface TerminalService { + + /** + * Returns all (active/inactive) {@link Terminal}s of the System. + * + * @return a List of all {@link Terminal}s + */ + List getAll(); + + + /** + * Returns all active {@link Terminal}s of the System. + * + * @return a List of active {@link Terminal}s + */ + List getAllActive(); + + + /** + * Saves a {@link Terminal}. + * + * @param terminal to save + * + * @return Terminal the saved {@link Terminal} + */ + Terminal save(Terminal terminal); + + + /** + * Returns a {@link Terminal} by its id or null if not found. + * + * @param id the id to search for + * + * @return the {@link Terminal} with the corresponding id or null + */ + Terminal getById(Long id); + + + /** + * Returns a {@link Terminal} by its uniqueId. + * + * @param uniqueId the uniqueId to search for + * + * @return the {@link Terminal} with the corresponding id or null + */ + Terminal getByUniqueId(BigInteger uniqueId); + + + /** + * Returns a {@link Terminal} by its latitude and longitude. + * + * @param location credentials to find terminal with + * + * @return {@link Terminal} with given {@link GeoLocation} + */ + Terminal getByGeoLocation(GeoLocation location); + + + /** + * Checks whether a {@link net.contargo.iris.terminal.Terminal} exists, + * + * @param uniqueId the terminal's uniqueId + * + * @return true if the terminal exists, else false + */ + boolean existsByUniqueId(BigInteger uniqueId); + + + /** + * Updates the {@link net.contargo.iris.terminal.Terminal} specified by its uniqueId with the values given in + * {@code terminalDto}. + * + * @param terminalUid the uniqueId of the terminal that should be updated + * @param terminal the dto providing values that should be updated + */ + Terminal updateTerminal(BigInteger terminalUid, Terminal terminal); +} diff --git a/src/main/java/net/contargo/iris/terminal/service/TerminalServiceImpl.java b/src/main/java/net/contargo/iris/terminal/service/TerminalServiceImpl.java new file mode 100644 index 00000000..88309d2c --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/service/TerminalServiceImpl.java @@ -0,0 +1,164 @@ +package net.contargo.iris.terminal.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.sequence.service.SequenceService; +import net.contargo.iris.terminal.Terminal; +import net.contargo.iris.terminal.persistence.TerminalRepository; + +import org.slf4j.Logger; + +import org.springframework.transaction.annotation.Transactional; + +import java.lang.invoke.MethodHandles; + +import java.math.BigInteger; + +import java.util.List; + +import javax.persistence.Entity; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Provides implementations related to {@link Terminal} entities. + * + * @author Sandra Thieme - thieme@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@Transactional +public class TerminalServiceImpl implements TerminalService { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private final TerminalRepository terminalRepository; + private final SequenceService uniqueIdSequenceService; + + public TerminalServiceImpl(TerminalRepository terminalRepository, SequenceService uniqueIdSequenceService) { + + this.terminalRepository = terminalRepository; + this.uniqueIdSequenceService = uniqueIdSequenceService; + } + + @Override + public List getAll() { + + return terminalRepository.findAll(); + } + + + @Override + public List getAllActive() { + + return terminalRepository.findByEnabled(true); + } + + + @Override + public synchronized Terminal save(Terminal terminal) { + + if (terminal.getUniqueId() == null) { + terminal.setUniqueId(determineUniqueId()); + } + + checkUniqueConstraints(terminal); + + return terminalRepository.save(terminal); + } + + + BigInteger determineUniqueId() { + + String entityName = Terminal.class.getAnnotation(Entity.class).name(); + BigInteger nextUniqueId = uniqueIdSequenceService.getNextId(entityName); + + boolean isUniqueIdAlreadyAssigned = terminalRepository.findByUniqueId(nextUniqueId) != null; + + while (isUniqueIdAlreadyAssigned) { + // In this loop we increment the ID by ourselves to avoid write-accesses to the DB for performance. + LOG.warn("Terminal uniqueId {} already assigned - trying next uniqueId", nextUniqueId); + nextUniqueId = nextUniqueId.add(BigInteger.ONE); + + if (!(terminalRepository.findByUniqueId(nextUniqueId) != null)) { + isUniqueIdAlreadyAssigned = false; + uniqueIdSequenceService.setNextId(entityName, nextUniqueId); + } + } + + return nextUniqueId; + } + + + @Override + public Terminal getById(Long id) { + + return terminalRepository.findOne(id); + } + + + @Override + public Terminal getByUniqueId(BigInteger uniqueId) { + + return terminalRepository.findByUniqueId(uniqueId); + } + + + @Override + public Terminal getByGeoLocation(GeoLocation location) { + + return terminalRepository.findByLatitudeAndLongitude(location.getLatitude(), location.getLongitude()); + } + + + @Override + public boolean existsByUniqueId(BigInteger uniqueId) { + + return getByUniqueId(uniqueId) != null; + } + + + @Override + public Terminal updateTerminal(BigInteger terminalUid, Terminal terminal) { + + Terminal savedTerminal = getByUniqueId(terminalUid); + savedTerminal.setEnabled(terminal.isEnabled()); + savedTerminal.setName(terminal.getName()); + savedTerminal.setLatitude(terminal.getLatitude()); + savedTerminal.setLongitude(terminal.getLongitude()); + savedTerminal.setRegion(terminal.getRegion()); + + return save(savedTerminal); + } + + + @Transactional(readOnly = true) + void checkUniqueConstraints(Terminal terminal) { + + boolean uniqueCoordinates; + boolean uniqueName; + + if (terminal.getId() == null) { + // create + uniqueCoordinates = terminalRepository.findByLatitudeAndLongitude(terminal.getLatitude(), + terminal.getLongitude()) == null; + + uniqueName = terminalRepository.findByName(terminal.getName()) == null; + } else { + // update + Long terminalId = terminal.getId(); + + uniqueCoordinates = terminalRepository.findByLatitudeAndLongitudeAndIdNot(terminal.getLatitude(), + terminal.getLongitude(), terminalId) == null; + + uniqueName = terminalRepository.findByNameAndIdNot(terminal.getName(), terminalId) == null; + } + + if (!uniqueCoordinates) { + throw new NonUniqueTerminalException("latitude", "longitude"); + } + + if (!uniqueName) { + throw new NonUniqueTerminalException("name"); + } + } +} diff --git a/src/main/java/net/contargo/iris/terminal/web/TerminalController.java b/src/main/java/net/contargo/iris/terminal/web/TerminalController.java new file mode 100644 index 00000000..0c6a730d --- /dev/null +++ b/src/main/java/net/contargo/iris/terminal/web/TerminalController.java @@ -0,0 +1,149 @@ +package net.contargo.iris.terminal.web; + +import net.contargo.iris.Message; +import net.contargo.iris.api.AbstractController; +import net.contargo.iris.sequence.service.UniqueIdSequenceServiceException; +import net.contargo.iris.terminal.Region; +import net.contargo.iris.terminal.Terminal; +import net.contargo.iris.terminal.service.NonUniqueTerminalException; +import net.contargo.iris.terminal.service.TerminalService; + +import org.slf4j.Logger; + +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.stereotype.Controller; + +import org.springframework.ui.Model; + +import org.springframework.validation.BindingResult; + +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +import java.lang.invoke.MethodHandles; + +import javax.validation.Valid; + +import static net.contargo.iris.api.AbstractController.SLASH; +import static net.contargo.iris.api.AbstractController.TERMINALS; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Controller Class for all operations with {@link net.contargo.iris.terminal.api.Terminal}s. + * + * @author Arnold Franke - franke@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +@Controller +@RequestMapping(SLASH + TERMINALS) +public class TerminalController extends AbstractController { + + private static final Logger LOG = getLogger(MethodHandles.lookup().lookupClass()); + + private static final String CONTROLLER_CONTEXT = "terminalManagement" + SLASH; + + private static final String TERMINALS_ATTRIBUTE = TERMINALS; + private static final String TERMINAL_ATTRIBUTE = "terminal"; + + private static final String TERMINALS_VIEW = TERMINALS; + private static final String TERMINAL_FORM_VIEW = "terminal"; + + private static final Message SAVE_SUCCESS_MESSAGE = Message.success("terminal.success.save.message"); + private static final Message UPDATE_SUCCESS_MESSAGE = Message.success("terminal.success.update.message"); + private static final String REGIONS_ATTRIBUTE = "regions"; + + private final TerminalService terminalService; + + @Autowired + public TerminalController(TerminalService terminalService) { + + this.terminalService = terminalService; + } + + @RequestMapping(value = "", method = RequestMethod.GET) + public String getAllTerminals(Model model) { + + model.addAttribute(TERMINALS_ATTRIBUTE, terminalService.getAll()); + + return CONTROLLER_CONTEXT + TERMINALS_VIEW; + } + + + @RequestMapping(value = SLASH + "new", method = RequestMethod.GET) + public String prepareForCreate(Model model) { + + model.addAttribute(TERMINAL_ATTRIBUTE, new Terminal()); + model.addAttribute(REGIONS_ATTRIBUTE, Region.values()); + + return CONTROLLER_CONTEXT + TERMINAL_FORM_VIEW; + } + + + @RequestMapping(value = SLASH, method = RequestMethod.POST) + public String saveTerminal(@Valid @ModelAttribute Terminal terminal, BindingResult result, Model model, + RedirectAttributes redirectAttributes) { + + return saveOrUpdateTerminal(model, result, terminal, redirectAttributes, SAVE_SUCCESS_MESSAGE); + } + + + @RequestMapping(value = SLASH + ID_PARAM, method = RequestMethod.GET) + public String getTerminal(@PathVariable Long id, Model model) { + + model.addAttribute(TERMINAL_ATTRIBUTE, terminalService.getById(id)); + model.addAttribute(REGIONS_ATTRIBUTE, Region.values()); + + return CONTROLLER_CONTEXT + TERMINAL_FORM_VIEW; + } + + + @RequestMapping(value = SLASH + ID_PARAM, method = RequestMethod.PUT) + public String updateTerminal(@Valid @ModelAttribute Terminal terminal, BindingResult result, @PathVariable Long id, + Model model, RedirectAttributes redirectAttributes) { + + Terminal dbTerminal = terminalService.getById(id); + + terminal.setId(dbTerminal.getId()); + + return saveOrUpdateTerminal(model, result, terminal, redirectAttributes, UPDATE_SUCCESS_MESSAGE); + } + + + private String saveOrUpdateTerminal(Model model, BindingResult result, Terminal terminal, + RedirectAttributes redirectAttributes, Message successMessage) { + + if (result.hasErrors()) { + model.addAttribute(TERMINAL_ATTRIBUTE, terminal); + + return CONTROLLER_CONTEXT + TERMINAL_FORM_VIEW; + } + + try { + redirectAttributes.addFlashAttribute(MESSAGE, successMessage); + + Long id = terminalService.save(terminal).getId(); + + return REDIRECT + WEBAPI_ROOT_URL + TERMINALS + SLASH + id; + } catch (NonUniqueTerminalException e) { + for (String fieldName : e.getBadFields()) { + result.rejectValue(fieldName, "terminal.nonunique." + fieldName); + } + + model.addAttribute(TERMINAL_ATTRIBUTE, terminal); + + return CONTROLLER_CONTEXT + TERMINAL_FORM_VIEW; + } catch (UniqueIdSequenceServiceException e) { + model.addAttribute(TERMINAL_ATTRIBUTE, terminal); + model.addAttribute(AbstractController.MESSAGE, UNIQUEID_ERROR_MESSAGE); + LOG.error(e.getMessage()); + + return CONTROLLER_CONTEXT + TERMINAL_FORM_VIEW; + } + } +} diff --git a/src/main/java/net/contargo/iris/truck/TruckRoute.java b/src/main/java/net/contargo/iris/truck/TruckRoute.java new file mode 100644 index 00000000..72dc4b76 --- /dev/null +++ b/src/main/java/net/contargo/iris/truck/TruckRoute.java @@ -0,0 +1,38 @@ +package net.contargo.iris.truck; + +import java.math.BigDecimal; + + +/** + * @author Marc Kannegiesser - kannegiesser@synyx.de + */ +public class TruckRoute { + + private final BigDecimal distance; + private final BigDecimal duration; + private final BigDecimal tollDistance; + + public TruckRoute(BigDecimal distance, BigDecimal tollDistance, BigDecimal duration) { + + this.distance = distance; + this.tollDistance = tollDistance; + this.duration = duration; + } + + public BigDecimal getDistance() { + + return distance; + } + + + public BigDecimal getDuration() { + + return duration; + } + + + public BigDecimal getTollDistance() { + + return tollDistance; + } +} diff --git a/src/main/java/net/contargo/iris/truck/service/OSRMNonRoutableRouteException.java b/src/main/java/net/contargo/iris/truck/service/OSRMNonRoutableRouteException.java new file mode 100644 index 00000000..1dbac5c9 --- /dev/null +++ b/src/main/java/net/contargo/iris/truck/service/OSRMNonRoutableRouteException.java @@ -0,0 +1,22 @@ +package net.contargo.iris.truck.service; + +/** + * Thrown when a OSRMJsonResponse getting a non routable route. + * + * @author Tobias Schneider - schneider@synyx.de + */ +public class OSRMNonRoutableRouteException extends RuntimeException { + + private static final long serialVersionUID = -4222863212840480759L; + + public OSRMNonRoutableRouteException(String message) { + + super(message); + } + + + public OSRMNonRoutableRouteException(String message, Exception cause) { + + super(message, cause); + } +} diff --git a/src/main/java/net/contargo/iris/truck/service/OSRMTruckRouteService.java b/src/main/java/net/contargo/iris/truck/service/OSRMTruckRouteService.java new file mode 100644 index 00000000..2aa31145 --- /dev/null +++ b/src/main/java/net/contargo/iris/truck/service/OSRMTruckRouteService.java @@ -0,0 +1,106 @@ +package net.contargo.iris.truck.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.osrm.service.OSRMQueryResult; +import net.contargo.iris.osrm.service.OSRMQueryService; +import net.contargo.iris.truck.TruckRoute; + +import org.springframework.cache.annotation.Cacheable; + +import java.math.BigDecimal; +import java.math.RoundingMode; + + +/** + * Service to get information of the truck route via OSRM. The toll will be calculates through the route instructions. + * Further information see below. + * + * @author Sven Mueller - mueller@synyx.de + * @author Tobias Schneider - schneider@synyx.de + */ +public class OSRMTruckRouteService implements TruckRouteService { + + private static final BigDecimal METERS_PER_KILOMETER = new BigDecimal("1000.0"); + private static final BigDecimal SECONDS_PER_MINUTE = new BigDecimal("60.0"); + private static final int STATUS_NO_ROUTE = 207; + private static final int SCALE = 5; + private static final int STANDARD_STREETDETAILS_LENGTH = 4; + private static final int INDEX_3 = 3; + private static final int INDEX_2 = 2; + + private final OSRMQueryService osrmQueryService; + + public OSRMTruckRouteService(OSRMQueryService osrmQueryService) { + + this.osrmQueryService = osrmQueryService; + } + + @Override + @Cacheable(value = "routingCache") + public TruckRoute route(GeoLocation start, GeoLocation destination) { + + OSRMQueryResult osrmResult = osrmQueryService.getOSRMXmlRoute(start, destination); + + if (osrmResult.getStatus() == STATUS_NO_ROUTE) { + throw new OSRMNonRoutableRouteException("Start: " + + start.toString() + " Destination: " + destination.toString() + " Status: " + osrmResult.getStatus()); + } + + BigDecimal toll = extractToll(osrmResult.getRouteInstructions()); + BigDecimal distance = new BigDecimal(osrmResult.getTotalDistance()).divide(METERS_PER_KILOMETER, SCALE, + RoundingMode.HALF_UP); + BigDecimal duration = new BigDecimal(osrmResult.getTotalTime()).divide(SECONDS_PER_MINUTE, SCALE, + RoundingMode.HALF_UP); + + return new TruckRoute(distance, toll, duration); + } + + + private BigDecimal extractToll(String[][] routeInstructions) { + + BigDecimal toll = BigDecimal.ZERO; + toll = toll.setScale(SCALE); + + for (String[] routeInstruction : routeInstructions) { + /* + * route_instructions: + * + * 0 - driving directions : integer numbers as defined in the source + * file DataStructures/TurnInstructions.h. + * + * 1 - way name (string) + * [ + * street_name ("A 65") + * street_type ("motorway") + * toll_route ("yes") + * country ("DE") + * ] + * + * 2 - length in meters (integer) + * + * 3 - position + * + * 4 - time + * + * 5 - length with unit (string) + * + * 6 - Direction abbreviation (string) N: north, S: south, E: east, W: west, NW: North West, ... + * + * 7 - azimuth (float) + */ + BigDecimal distance = new BigDecimal(routeInstruction[2]); + distance = distance.divide(METERS_PER_KILOMETER); + + String[] streetDetails = routeInstruction[1].split("/;"); + + // currently, only german ("DE") toll roads are considered. + if (streetDetails.length == STANDARD_STREETDETAILS_LENGTH + && ("yes".equalsIgnoreCase(streetDetails[INDEX_2]) + && "de".equalsIgnoreCase(streetDetails[INDEX_3]))) { + toll = toll.add(distance); + } + } + + return toll; + } +} diff --git a/src/main/java/net/contargo/iris/truck/service/TruckRouteService.java b/src/main/java/net/contargo/iris/truck/service/TruckRouteService.java new file mode 100644 index 00000000..93900036 --- /dev/null +++ b/src/main/java/net/contargo/iris/truck/service/TruckRouteService.java @@ -0,0 +1,24 @@ + +package net.contargo.iris.truck.service; + +import net.contargo.iris.GeoLocation; +import net.contargo.iris.truck.TruckRoute; + + +/** + * Generates routings for Trucks. + * + * @author Sven Mueller - mueller@synyx.de + */ +public interface TruckRouteService { + + /** + * Generate a truck routing. + * + * @param start The start coordinates + * @param destination The destination coordinates + * + * @return The calculated routing + */ + TruckRoute route(GeoLocation start, GeoLocation destination); +} diff --git a/src/main/java/net/contargo/iris/util/BigDecimalComparator.java b/src/main/java/net/contargo/iris/util/BigDecimalComparator.java new file mode 100644 index 00000000..0969b46b --- /dev/null +++ b/src/main/java/net/contargo/iris/util/BigDecimalComparator.java @@ -0,0 +1,34 @@ +package net.contargo.iris.util; + +import java.io.Serializable; + +import java.math.BigDecimal; + +import java.util.Comparator; + + +/** + * @author Arnold Franke - franke@synyx.de + */ +public class BigDecimalComparator implements Comparator, Serializable { + + private static final long serialVersionUID = -4798283898023294179L; + + @Override + public int compare(BigDecimal o1, BigDecimal o2) { + + if (o1 == null && o2 == null) { + return 0; + } + + if (o1 == null) { + return 1; + } + + if (o2 == null) { + return -1; + } + + return o1.compareTo(o2); + } +} diff --git a/src/main/java/net/contargo/iris/util/HttpUtil.java b/src/main/java/net/contargo/iris/util/HttpUtil.java new file mode 100644 index 00000000..87460731 --- /dev/null +++ b/src/main/java/net/contargo/iris/util/HttpUtil.java @@ -0,0 +1,95 @@ +package net.contargo.iris.util; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; + +import java.lang.invoke.MethodHandles; + + +/** + * Util class for dealing with Http stuff. + * + * @author Aljona Murygina - murygina@synyx.de + */ +public class HttpUtil { + + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final int STATUS_CODE_300 = 300; + private static final int STATUS_CODE_199 = 199; + private final HttpClient httpClient; + + public HttpUtil(HttpClient httpClient) { + + this.httpClient = httpClient; + } + + /** + * Gets the response as String for the given String representing an URL. + * + * @param url String + * + * @return response as String + */ + public String getResponseContent(String url) { + + HttpEntity entity = getHttpEntityForGETRequest(url); + String response = null; + + if (entity != null) { + try(InputStream is = entity.getContent()) { + response = InputStreamUtil.convertInputStreamToString(is); + } catch (IOException ex) { + throw new HttpUtilException("Could not get content (InputStream) of HttpEntity", ex); + } + } + + return response; + } + + + /** + * Sends GET request to the given url and returns the response's HttpEntity. + * + * @param url String + * + * @return HttpEntity + */ + private HttpEntity getHttpEntityForGETRequest(String url) { + + HttpEntity entity; + + try { + LOG.debug("Sending GET request to {} ", url); + + HttpGet httpGet = new HttpGet(url); + HttpResponse response = httpClient.execute(httpGet); + + int statusCode = response.getStatusLine().getStatusCode(); + + if (statusCode > STATUS_CODE_199 && statusCode < STATUS_CODE_300) { + LOG.debug("Received response with status code {} for URL {}", statusCode, url); + } else { + LOG.error("Problem requesting {} - Code {} - {}", url, statusCode, response.getStatusLine()); + } + + entity = response.getEntity(); + + if (entity == null) { + LOG.warn("No response content found for url {} ", url); + } + } catch (IOException ex) { + throw new HttpUtilException("Error getting response for url " + url, ex); + } + + return entity; + } +} diff --git a/src/main/java/net/contargo/iris/util/HttpUtilException.java b/src/main/java/net/contargo/iris/util/HttpUtilException.java new file mode 100644 index 00000000..fc1661aa --- /dev/null +++ b/src/main/java/net/contargo/iris/util/HttpUtilException.java @@ -0,0 +1,14 @@ +package net.contargo.iris.util; + +/** + * Exception thrown if anything goes awry in {@link net.contargo.iris.util.HttpUtil}. + * + * @author Sandra Thieme - thieme@synyx.de + */ +public class HttpUtilException extends RuntimeException { + + public HttpUtilException(String message, Throwable t) { + + super(message, t); + } +} diff --git a/src/main/java/net/contargo/iris/util/InputStreamUtil.java b/src/main/java/net/contargo/iris/util/InputStreamUtil.java new file mode 100644 index 00000000..b4a638c2 --- /dev/null +++ b/src/main/java/net/contargo/iris/util/InputStreamUtil.java @@ -0,0 +1,109 @@ + +package net.contargo.iris.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.core.io.ClassPathResource; + +import org.w3c.dom.Document; + +import org.xml.sax.SAXException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + + +/** + * This is a helper class for actions concerning {@link java.io.InputStream}s. + * + * @author Aljona Murygina - murygina@synyx.de + */ +public final class InputStreamUtil { + + private static final Logger LOG = LoggerFactory.getLogger(InputStreamUtil.class); + + private InputStreamUtil() { + } + + /** + * Gets the file with the given path plus file name of the classpath and converts it to an InputStream. + * + * @param fileName + * + * @return InputStream + */ + public static InputStream getFileInputStream(String fileName) { + + InputStream in = null; + + try { + ClassPathResource resource = new ClassPathResource(fileName); + File file = resource.getFile(); + in = new FileInputStream(file); + } catch (IOException ex) { + LOG.warn("File '" + fileName + "' could not be converted to InputStream.", ex); + } + + return in; + } + + + /** + * Converts the given InputStream to String. + * + * @param in InputStream + * + * @return String content of the InputStream + */ + public static String convertInputStreamToString(InputStream in) throws IOException { + + StringBuilder builder = new StringBuilder(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8")); + + String line = reader.readLine(); + + while (line != null) { + builder.append(line); + line = reader.readLine(); + } + + return builder.toString(); + } + + + /** + * Converts {@link java.io.InputStream} to {@link org.w3c.dom.Document}. + * + * @param in InputStream + * + * @return Document + */ + public static Document getDocument(InputStream in) { + + Document doc = null; + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + DocumentBuilder docBuilder = null; + + try { + docBuilder = factory.newDocumentBuilder(); + + doc = docBuilder.parse(in); + } catch (ParserConfigurationException | SAXException | IOException ex) { + LOG.warn("Error fetching xml document.", ex); + } + + return doc; + } +} diff --git a/src/main/java/net/contargo/iris/web/RootController.java b/src/main/java/net/contargo/iris/web/RootController.java new file mode 100644 index 00000000..6c102411 --- /dev/null +++ b/src/main/java/net/contargo/iris/web/RootController.java @@ -0,0 +1,33 @@ +package net.contargo.iris.web; + +import net.contargo.iris.api.AbstractController; + +import org.springframework.stereotype.Controller; + +import org.springframework.ui.Model; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + + +/** + * @author Sandra Thieme - thieme@synyx.de + */ +@Controller +public class RootController extends AbstractController { + + public static final String ROOT = SLASH; + + @RequestMapping(value = SLASH + STAR, method = RequestMethod.GET) + public String index(Model model) { + + return INDEX; + } + + + @RequestMapping(value = SLASH + TRIANGLE + SLASH, method = RequestMethod.GET) + public String triangle() { + + return TRIANGLE_VIEW; + } +} diff --git a/src/main/resources/application-context.xml b/src/main/resources/application-context.xml new file mode 100644 index 00000000..078ee843 --- /dev/null +++ b/src/main/resources/application-context.xml @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/application-jenkins.properties b/src/main/resources/application-jenkins.properties new file mode 100644 index 00000000..08792ff0 --- /dev/null +++ b/src/main/resources/application-jenkins.properties @@ -0,0 +1,22 @@ +# Settings for using Nominatim to geocode addresses +# http://nominatim.openstreetmap.org/ +# For further information about query parameters have a look at: http://wiki.openstreetmap.org/wiki/Nominatim + +nominatim.base.url=http://maps.contargo.net/nominatim/ +osrm.url=http://maps2.contargo.net/osrm/viaroute +error.reports.basepath=error-reports + +# preferred language for showing search results, overrides browser language +nominatim.language=de + +application.version=${project.groupId}:${project.artifactId}:${project.version} - build at ${buildTime} by ${user.name} + + +#Database +liquibase.context= +database.driver=com.mysql.jdbc.Driver +database.url=jdbc:mysql://localhost:3306/irisci +database.username=root +database.password= +hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect +hibernate.hbm2ddl.auto=validate diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 00000000..d4c1788e --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,23 @@ +# Settings for using Nominatim to geocode addresses +# http://nominatim.openstreetmap.org/ +# For further information about query parameters have a look at: http://wiki.openstreetmap.org/wiki/Nominatim + +nominatim.base.url=http://maps.contargo.net/nominatim/ +osrm.url=http://maps.contargo.net/osrm/viaroute +error.reports.basepath=error-reports + +# preferred language for showing search results, overrides browser language +nominatim.language=de + +application.version=${project.groupId}:${project.artifactId}:${project.version} - build at ${buildTime} by ${user.name} + +#Database +liquibase.context= +database.driver=com.mysql.jdbc.Driver +database.url=jdbc:mysql://localhost:3306/iris +database.username=root +database.password= +hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect +hibernate.hbm2ddl.auto=validate + +logback.configuration.file=logback.xml \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.10-add-not-null-constraint-to-terminal-region.xml b/src/main/resources/dbchangelogs/db.changelog-0.10-add-not-null-constraint-to-terminal-region.xml new file mode 100644 index 00000000..c371579a --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.10-add-not-null-constraint-to-terminal-region.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.11-add-column-hashkey-to-staticaddress.xml b/src/main/resources/dbchangelogs/db.changelog-0.11-add-column-hashkey-to-staticaddress.xml new file mode 100644 index 00000000..30a23273 --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.11-add-column-hashkey-to-staticaddress.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.2-add-notnull-static-address.xml b/src/main/resources/dbchangelogs/db.changelog-0.2-add-notnull-static-address.xml new file mode 100644 index 00000000..4efac1f3 --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.2-add-notnull-static-address.xml @@ -0,0 +1,18 @@ + + + + + Add NOT-NULL constraints for columns postalcode, city and cityNormalized + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.2-create-connection.xml b/src/main/resources/dbchangelogs/db.changelog-0.2-create-connection.xml new file mode 100644 index 00000000..05bec3bd --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.2-create-connection.xml @@ -0,0 +1,46 @@ + + + + + Create table for connections + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.2-create-seaport.xml b/src/main/resources/dbchangelogs/db.changelog-0.2-create-seaport.xml new file mode 100644 index 00000000..742d0740 --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.2-create-seaport.xml @@ -0,0 +1,34 @@ + + + + + Create table for seaports + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.2-create-static-address.xml b/src/main/resources/dbchangelogs/db.changelog-0.2-create-static-address.xml new file mode 100644 index 00000000..a76c544e --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.2-create-static-address.xml @@ -0,0 +1,57 @@ + + + + + Create table for static address list + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.2-create-terminal.xml b/src/main/resources/dbchangelogs/db.changelog-0.2-create-terminal.xml new file mode 100644 index 00000000..b2e5c918 --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.2-create-terminal.xml @@ -0,0 +1,34 @@ + + + + + Create table for terminals + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.3-Unique_constraint_main_run_connections.xml b/src/main/resources/dbchangelogs/db.changelog-0.3-Unique_constraint_main_run_connections.xml new file mode 100644 index 00000000..49d7b271 --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.3-Unique_constraint_main_run_connections.xml @@ -0,0 +1,14 @@ + + + + + Add unique constraint to connections + + + + + + diff --git a/src/main/resources/dbchangelogs/db.changelog-0.3-add-column-region-to-terminal.xml b/src/main/resources/dbchangelogs/db.changelog-0.3-add-column-region-to-terminal.xml new file mode 100644 index 00000000..13979db1 --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.3-add-column-region-to-terminal.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.6-add-column-uniqueId-to-terminal-seaport.xml b/src/main/resources/dbchangelogs/db.changelog-0.6-add-column-uniqueId-to-terminal-seaport.xml new file mode 100644 index 00000000..8b3bd9c6 --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.6-add-column-uniqueId-to-terminal-seaport.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.6-add-uniquie-contraints-to-terminal-seaport.xml b/src/main/resources/dbchangelogs/db.changelog-0.6-add-uniquie-contraints-to-terminal-seaport.xml new file mode 100644 index 00000000..32647c20 --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.6-add-uniquie-contraints-to-terminal-seaport.xml @@ -0,0 +1,29 @@ + + + + + + Add unique constraints on both the terminal's name and its coordinates. + + + + + + + + + + Add unique constraints on both the seaport's name and its coordinates. + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.7-add-column-uniqueId-to-staticaddress.xml b/src/main/resources/dbchangelogs/db.changelog-0.7-add-column-uniqueId-to-staticaddress.xml new file mode 100644 index 00000000..1d15a4fc --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.7-add-column-uniqueId-to-staticaddress.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.7-create-uniqueIdSequence.xml b/src/main/resources/dbchangelogs/db.changelog-0.7-create-uniqueIdSequence.xml new file mode 100644 index 00000000..b1ed4d2b --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.7-create-uniqueIdSequence.xml @@ -0,0 +1,45 @@ + + + + + Create table for managing id sequences + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-0.9-fix-terminal-idx.xml b/src/main/resources/dbchangelogs/db.changelog-0.9-fix-terminal-idx.xml new file mode 100644 index 00000000..48080eaa --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-0.9-fix-terminal-idx.xml @@ -0,0 +1,17 @@ + + + + + Fix terminal index + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-1.6-add-route-data-revisions.xml b/src/main/resources/dbchangelogs/db.changelog-1.6-add-route-data-revisions.xml new file mode 100644 index 00000000..ff4e84f0 --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-1.6-add-route-data-revisions.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/dbchangelogs/db.changelog-master.xml b/src/main/resources/dbchangelogs/db.changelog-master.xml new file mode 100644 index 00000000..08cb72ff --- /dev/null +++ b/src/main/resources/dbchangelogs/db.changelog-master.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/ehcache.xml b/src/main/resources/ehcache.xml new file mode 100644 index 00000000..5ade5f05 --- /dev/null +++ b/src/main/resources/ehcache.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/jpa-context.xml b/src/main/resources/jpa-context.xml new file mode 100644 index 00000000..b839bbb0 --- /dev/null +++ b/src/main/resources/jpa-context.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/logback-access.xml b/src/main/resources/logback-access.xml new file mode 100644 index 00000000..84f088f8 --- /dev/null +++ b/src/main/resources/logback-access.xml @@ -0,0 +1,42 @@ + + + + + + countingFilter + + + + logs/iris-access.log + + logs/backup-access/iris-access.%d{yyyy-MM}.log.gz + 4 + + + + combined + + + + + + %n%fullRequest%n%fullResponse%n + + + + + logs/iris-req-resp.log + + logs/backup-access/iris-req-resp.%d{yyyy-MM}.log.gz + 4 + + + + %n%fullRequest%n%fullResponse%n + + + + + + + \ No newline at end of file diff --git a/src/main/resources/logback-prod.xml b/src/main/resources/logback-prod.xml new file mode 100644 index 00000000..8a424ccd --- /dev/null +++ b/src/main/resources/logback-prod.xml @@ -0,0 +1,41 @@ + + + + + true + + + + + + + logs/iris.log + + logs/backup/iris.%d{yyyy-MM}.log.gz + 4 + + + + %d | %5p | %-4relative [%thread] | %logger{35} - %msg %n + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 00000000..4883a83c --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,48 @@ + + + + + true + + + + + + + + %d | %5p | %t | %-55logger{55} | %m %n + + + + + logs/iris.log + + logs/backup/iris.%d{yyyy-MM}.log.gz + 4 + + + + %d | %5p | %-4relative [%thread] | %logger{35} - %msg%n + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/messages/messages.properties b/src/main/resources/messages/messages.properties new file mode 100644 index 00000000..c0ba53b5 --- /dev/null +++ b/src/main/resources/messages/messages.properties @@ -0,0 +1,140 @@ +################################## index.jsp +index.page.title=IRIS - Welcome +index.page.clients=IRIS Clients +index.page.clients.description=IRIS can be accessed conveniently via a modern web application. +index.page.triangle=Triangular Traffic +index.page.triangle.description=Using the Triangular Traffic you are able to route multistop truck routes via terminals and arbitrary addresses in any order. +index.page.search.api=Search API +index.page.docs.api=API Documentation +index.page.restful=RESTful Webservice +index.page.restful.description=For automation, a RESTful web service is provided. The api is self-explanatory, and can be accessed with your web browser as well. +index.page.what.is.iris=What is IRIS? +index.page.what.is.iris.name=Intermodal Routing Information System +index.page.what.is.iris.vision=Vision +index.page.what.is.iris.vision.description.1=The goal of IRIS is to create a centralized and transparent database for distance calculations in short-haul goods transport. IRIS aims to provide such a platform, for both clients and service providers. +index.page.what.is.iris.vision.description.2=IRIS is used to calculate truckings for transports from a hinterland terminal to the place of loading and the other way around. The place of loading can be set very detailed by adjusting its coordinates. IRIS uses these coordinates to calculate the truck route, distance, toll kilometers and the duration of the trucking. An extension for barge and rail routings is already planned. +index.page.what.is.iris.vision.description.3=Example: A freight container arrives in Mannheim via cargo ship and needs to be transported to 48 Berliner Str., Heidelberg, Germany. After arriving at the Contargo terminal in Mannheim it needs to be loaded on a truck to be transported to its destination. There, the recipient unloads the content and the truck transports the empty container back to the terminal. +index.page.remarks=Remarks +index.page.remarks.1=Distances are road distances. Their values depend on which route is selected between starting point and destination. +index.page.remarks.2=IRIS also takes streets with restrictions for trucks into consideration. +index.page.remarks.3=Currently, IRIS does not support car ferries, for example between France and the UK. +index.page.remarks.4=Currently, German motorways and highways (Autobahn, Bundesstra\u00dfe) are the only toll roads considered. Due to, the total toll distance only reflect the mentioned roads. +index.page.remarks.5=Contargo assumes no guarantee and no liability for the data and information delivered and made available by them being correct, complete, and up-to-date. +index.page.remarks.6=Due to changes in the infrastructure there can be differences in the outcome of your routings from time to time. We try to catch up with the latest changes as soon as possible. +index.page.remarks.7=The duration of truckings depends on the database to which IRIS refers. Meanderings due to traffic jams and building sites are not taken into consideration. + +############################ general-error.jsp +iris.internal.error=An unexpected error occured :-( +iris.error.report=Please report this code to an IRIS admin\: {0} + +################################## MAIN +management.page.title=IRIS - Management + +table.newLabel=New +table.editLabel=Edit + +form.backTitle=Back +form.save=Save +form.update=Update + +page.navbar.index=Startpage +page.navbar.terminalmanagement=Terminals +page.navbar.seaportmanagement=Seaports +page.navbar.connectionmanagement=Connections +page.navbar.staticaddresses=Static Addresses +page.navbar.trianglerouting=Triangular Traffic +page.toTopScrollerToolTip=Back to top + +mappicker.chooselocation=Choose geolocation +mappicker.close=Close +mappicker.closebutton=x +mappicker.select=Select + +uniqueid.error.message=Error generating new unique identifier. Please contact an Administrator. + +terminals.page.title=IRIS - Terminal management +terminal.create.title=Save terminal +terminal.update.title=Update terminal +terminal.name=Name +terminal.region=Region +terminal.enabled=Enabled +terminal.metaInformation=Information +terminal.success.save.message=Terminal saved successfully. +terminal.success.update.message=Terminal updated successfully. +terminal.nonunique.latitude=Coordinates have to be unique. +terminal.nonunique.longitude=Coordinates have to be unique. +terminal.nonunique.name=Name has to be unique. + +seaports.page.title=IRIS - Seaport management +seaport.create.title=Save seaport +seaport.update.title=Update seaport +seaport.name=Name +seaport.enabled=Enabled +seaport.metaInformation=Information +seaport.success.save.message=Seaport saved successfully. +seaport.success.update.message=Seaport updated successfully. +seaport.nonunique.latitude=Coordinates have to be unique. +seaport.nonunique.longitude=Coordinates have to be unique. +seaport.nonunique.name=Name has to be unique. + +geolocation.latitude=Latitude +geolocation.longitude=Longitude + +mainrunconnection.page.title=IRIS - Connection management +mainrunconnection.seaport.name=Seaport +mainrunconnection.seaport.enabled=Seaport enabled? +mainrunconnection.terminal.name=Terminal +mainrunconnection.terminal.enabled=Terminal enabled? +mainrunconnection.routeType=Type +mainrunconnection.enabled=Connection enabled +mainrunconnection.everythingenabled=Everything enabled? +mainrunconnection.terminal.isenabled.true=(enabled) +mainrunconnection.terminal.isenabled.false=(NOT enabled!) +mainrunconnection.seaport.isenabled.true=(enabled) +mainrunconnection.seaport.isenabled.false=(NOT enabled!) +mainrunconnection.dieselDistance=Diesel km +mainrunconnection.electricDistance=Electrical km +mainrunconnection.create.title=Create new main run connection +mainrunconnection.duplicate.connection.seaport=This combination of seaport... +mainrunconnection.duplicate.connection.terminal=... and terminal with ... +mainrunconnection.duplicate.connection.routetype=.. this route type is already defined. +mainrunconnection.update.title=Edit main run connection - +mainrunconnection.success.save.message = Main run connection saved successfully. +mainrunconnection.success.edit.message = Main run connection updated successfully. + +staticaddress.page.title=IRIS - Static Addresses management +staticaddress.label.search=Search +staticaddress.label.postalcode=Postalcode +staticaddress.label.city=City +staticaddress.label.suburb=Suburb +staticaddress.label.country=Country +staticaddress.label.lat=Latitude +staticaddress.label.lon=Longitude +staticaddress.label.hashkey=Hashkey +staticaddress.editmode.create=Create Static Address +staticaddress.editmode.edit=Update Static Address +staticaddress.error.duplicate=There is already a static address with same city, suburb and postalcode. +staticaddress.error.coordinates.duplicate=There is already a static address with same latitude and longitude. +staticaddress.save.success=Static address saved successfully. +staticaddress.update.success=Static address updated successfully. + +triangle.page.title=Triangular Traffic + +route.type.barge=Barge +route.type.rail=Rail +route.type.truck=Truck + + +############################ login.jsp +login.title=Login +login.use=Use IRIS +login.signin=Sign In +login.email.label=E-Mail +login.password.label=Password +login.description=If you already have a user account you can login here. +login.error.title=Login failed +login.error.text=You have probably used the wrong email or password. Please try again to login with correct credentials. + +browser.too.old.warningTitle=Unsupported Browser +browser.too.old.warning=Your browser is too old. We recommend an updated Google Chrome or Firefox. Or at least a newer version of Internet Explorer (>= 8). +browser.too.old.popup.warning=You are using an unsupported browser! \ No newline at end of file diff --git a/src/main/resources/nominatim/json_sample.json b/src/main/resources/nominatim/json_sample.json new file mode 100644 index 00000000..ba556241 --- /dev/null +++ b/src/main/resources/nominatim/json_sample.json @@ -0,0 +1,5 @@ +[{"place_id":"84069287","licence":"Data Copyright OpenStreetMap Contributors, Some Rights Reserved. CC-BY-SA 2.0.","osm_type":"way","osm_id":"90085697","boundingbox":["49.002025604248","49.0021591186523","8.39366722106934","8.39412879943848"],"lat":"49.002095196515","lon":"8.39391718305592","display_name":"Fotostudio Becker, 68, Karlstrasse, Suedweststadt, Karlsruhe, Regierungsbezirk Karlsruhe, Baden-Wuerttemberg, 76137, Federal Republic of Germany (land mass)","class":"amenity","type":"photo_studio","address":{"amenity":"Fotostudio Becker","house_number":"68","road":"Karlstraße","suburb":"Suedweststadt","city":"Karlsruhe","county":"Karlsruhe","state_district":"Regierungsbezirk Karlsruhe","state":"Baden-Wuerttemberg","postcode":"76137","country":"Federal Republic of Germany (land mass)","country_code":"de"}}, +{"place_id":"42804539","licence":"Data Copyright OpenStreetMap Contributors, Some Rights Reserved. CC-BY-SA 2.0.","osm_type":"way","osm_id":"34185237","boundingbox":["48.9919967651367","48.9927024841309","8.39306163787842","8.39326763153076"],"lat":"48.9923342170845","lon":"8.39320538751983","display_name":"Karlstraße, Beiertheim-Bulach, Karlsruhe, Regierungsbezirk Karlsruhe, Baden-Wuerttemberg, 76137, Federal Republic of Germany (land mass)","class":"highway","type":"tertiary","address":{"road":"Karlstraße","suburb":"Beiertheim-Bulach","city":"Karlsruhe","county":"Karlsruhe","state_district":"Regierungsbezirk Karlsruhe","state":"Baden-Wuerttemberg","postcode":"76137","country":"Federal Republic of Germany (land mass)","country_code":"de"}}, +{"place_id":"18535393","licence":"Data Copyright OpenStreetMap Contributors, Some Rights Reserved. CC-BY-SA 2.0.","osm_type":"way","osm_id":"4230619","boundingbox":["48.9971389770508","48.9983367919922","8.39372158050537","8.39389991760254"],"lat":"48.9977192676553","lon":"8.39385460879033","display_name":"Karlstraße, Suedweststadt, Karlsruhe, Regierungsbezirk Karlsruhe, Baden-Wuerttemberg, 76137, Federal Republic of Germany (land mass)","class":"highway","type":"secondary","address":{"road":"Karlstraße","suburb":"Suedweststadt","city":"Karlsruhe","county":"Karlsruhe","state_district":"Regierungsbezirk Karlsruhe","state":"Baden-Wuerttemberg","postcode":"76137","country":"Federal Republic of Germany (land mass)","country_code":"de"}}, +{"place_id":"84069287","licence":"Data Copyright OpenStreetMap Contributors, Some Rights Reserved. CC-BY-SA 2.0.","osm_type":"way","osm_id":"90085697","boundingbox":["49.002025604248","49.0021591186523","8.39366722106934","8.39412879943848"],"lat":"49.002095196515","lon":"8.39391718305592","display_name":"Fotostudio Becker, 68, Karlstraße, Suedweststadt, Karlsruhe, Regierungsbezirk Karlsruhe, Baden-Wuerttemberg, 76137, Federal Republic of Germany (land mass)","class":"amenity","type":"photo_studio","address":{"amenity":"Fotostudio Becker","house_number":"68","road":"Karlstraße","suburb":"Suedweststadt","city":"Karlsruhe","county":"Karlsruhe","state_district":"Regierungsbezirk Karlsruhe","state":"Baden-Wuerttemberg","postcode":"76137","country":"Federal Republic of Germany (land mass)","country_code":"de"}}, +{"place_id":"18535965","licence":"Data Copyright OpenStreetMap Contributors, Some Rights Reserved. CC-BY-SA 2.0.","osm_type":"way","osm_id":"4230623","boundingbox":["49.005012512207","49.0055770874023","8.39449977874756","8.39451885223389"],"lat":"49.0052941887663","lon":"8.39451053751155","display_name":"Karlstraße, Suedweststadt, Karlsruhe, Regierungsbezirk Karlsruhe, Baden-Wuerttemberg, 76133, Federal Republic of Germany (land mass)","class":"highway","type":"secondary","address":{"road":"Karlstraße","suburb":"Suedweststadt","city":"Karlsruhe","county":"Karlsruhe","state_district":"Regierungsbezirk Karlsruhe","state":"Baden-Wuerttemberg","postcode":"76133","country":"Federal Republic of Germany (land mass)","country_code":"de"}}] diff --git a/src/main/resources/nominatim/json_sample_single_result.json b/src/main/resources/nominatim/json_sample_single_result.json new file mode 100644 index 00000000..69f7c2ab --- /dev/null +++ b/src/main/resources/nominatim/json_sample_single_result.json @@ -0,0 +1,23 @@ +{ + "place_id": "84069287", + "licence": "Data Copyright OpenStreetMap Contributors, Some Rights Reserved. CC-BY-SA 2.0.", + "osm_type": "way", + "osm_id": "90085697", + "boundingbox": ["49.002025604248", "49.0021591186523", "8.39366722106934", "8.39412879943848"], + "lat": "49.002095196515", "lon": "8.39391718305592", + "display_name": "Fotostudio Becker, 68, Karlstrasse, Suedweststadt, Karlsruhe, Regierungsbezirk Karlsruhe, Baden-Wuerttemberg, 76137, Federal Republic of Germany (land mass)", "class": "amenity", + "type": "photo_studio", + "address": { + "amenity": "Fotostudio Becker", + "house_number": "68", + "road": "Karlstraße", + "suburb": "Suedweststadt", + "city": "Karlsruhe", + "county": "Karlsruhe", + "state_district": "Regierungsbezirk Karlsruhe", + "state": "Baden-Wuerttemberg", + "postcode": "76137", + "country": "Federal Republic of Germany (land mass)", + "country_code": "de" + } +} \ No newline at end of file diff --git a/src/main/resources/openrouteservice/ors_geocoding_sample.xml b/src/main/resources/openrouteservice/ors_geocoding_sample.xml new file mode 100644 index 00000000..01cab6f5 --- /dev/null +++ b/src/main/resources/openrouteservice/ors_geocoding_sample.xml @@ -0,0 +1,67 @@ + + + + + + + + + 8.4044366 49.0140679 + + + + + + Baden-Württemberg + Karlsruhe + 76131 + + + + + + 7.2931383 51.1526207 + + + + + + Nordrhein-Westfalen + Karlsruhe + 42897 + + + + + + 8.39390170659984 49.0020971544562 + + + + + + + Baden-Württemberg + Karlsruhe + 76137 + + + + + + 8.39390170659984 49.0020971544562 + + + + + + Baden-Württemberg + Karlsruhe + 76137 + + + + + + + \ No newline at end of file diff --git a/src/main/resources/public-api-context.xml b/src/main/resources/public-api-context.xml new file mode 100644 index 00000000..fd5a3ea6 --- /dev/null +++ b/src/main/resources/public-api-context.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/usercredentials-ci.properties b/src/main/resources/usercredentials-ci.properties new file mode 100644 index 00000000..f31ddd40 --- /dev/null +++ b/src/main/resources/usercredentials-ci.properties @@ -0,0 +1,2 @@ +admin@example.com=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918,ROLE_ADMIN,enabled +user@example.com=04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb,ROLE_USER,enabled \ No newline at end of file diff --git a/src/main/resources/usercredentials-dev.properties b/src/main/resources/usercredentials-dev.properties new file mode 100644 index 00000000..f31ddd40 --- /dev/null +++ b/src/main/resources/usercredentials-dev.properties @@ -0,0 +1,2 @@ +admin@example.com=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918,ROLE_ADMIN,enabled +user@example.com=04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb,ROLE_USER,enabled \ No newline at end of file diff --git a/src/main/resources/usercredentials-jenkins.properties b/src/main/resources/usercredentials-jenkins.properties new file mode 100644 index 00000000..f31ddd40 --- /dev/null +++ b/src/main/resources/usercredentials-jenkins.properties @@ -0,0 +1,2 @@ +admin@example.com=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918,ROLE_ADMIN,enabled +user@example.com=04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb,ROLE_USER,enabled \ No newline at end of file diff --git a/src/main/resources/web-api-context.xml b/src/main/resources/web-api-context.xml new file mode 100644 index 00000000..33e68600 --- /dev/null +++ b/src/main/resources/web-api-context.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/web-security.xml b/src/main/resources/web-security.xml new file mode 100644 index 00000000..77f9186b --- /dev/null +++ b/src/main/resources/web-security.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/decorators.xml b/src/main/webapp/WEB-INF/decorators.xml new file mode 100644 index 00000000..df6c8f7d --- /dev/null +++ b/src/main/webapp/WEB-INF/decorators.xml @@ -0,0 +1,14 @@ + + + + + + **/help/* + + + + + * + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/sitemesh.xml b/src/main/webapp/WEB-INF/sitemesh.xml new file mode 100644 index 00000000..1c0f3bdb --- /dev/null +++ b/src/main/webapp/WEB-INF/sitemesh.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/tags/checkboxFieldNormal.tag b/src/main/webapp/WEB-INF/tags/checkboxFieldNormal.tag new file mode 100644 index 00000000..196552d9 --- /dev/null +++ b/src/main/webapp/WEB-INF/tags/checkboxFieldNormal.tag @@ -0,0 +1,18 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + +<%@ attribute name="property" type="java.lang.String" required="true"%> +<%@ attribute name="messageKey" type="java.lang.String" required="true"%> +<%@ attribute name="size" type="java.lang.String" required="false"%> + +
+ + + +
+
+ +
+
+
\ No newline at end of file diff --git a/src/main/webapp/WEB-INF/tags/inputField.tag b/src/main/webapp/WEB-INF/tags/inputField.tag new file mode 100644 index 00000000..85635fda --- /dev/null +++ b/src/main/webapp/WEB-INF/tags/inputField.tag @@ -0,0 +1,44 @@ +<%@ tag language="java" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + +<%@ attribute name="property" type="java.lang.String" required="true"%> +<%@ attribute name="messageKey" type="java.lang.String" required="true"%> + +<%@ attribute name="size" type="java.lang.String" required="false"%> +<%@ attribute name="maxlength" type="java.lang.String" required="false"%> +<%@ attribute name="title" type="java.lang.String" required="false"%> +<%@ attribute name="readonly" type="java.lang.Boolean" required="false"%> +<%@ attribute name="unitMessageKey" type="java.lang.String" required="false"%> +<%@ attribute name="cssClass" type="java.lang.String" required="false"%> +<%@ attribute name="onkeypress" type="java.lang.String" required="false"%> + +
+ + + + +
+ + + + + ${unitMessageKey} + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/src/main/webapp/WEB-INF/tags/mappicker.tag b/src/main/webapp/WEB-INF/tags/mappicker.tag new file mode 100644 index 00000000..e1c79fd8 --- /dev/null +++ b/src/main/webapp/WEB-INF/tags/mappicker.tag @@ -0,0 +1,30 @@ +<%@ tag language="java" pageEncoding="UTF-8"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + +<%@ attribute name="latitudeId" type="java.lang.String" required="true"%> +<%@ attribute name="longitudeId" type="java.lang.String" required="true"%> + +" rel="stylesheet"> + + + + + +<%--link to display modal dialog--%> + + +<%-- modal dialog --%> + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/tags/oldBrowserWarning.tag b/src/main/webapp/WEB-INF/tags/oldBrowserWarning.tag new file mode 100644 index 00000000..c1e3dc7b --- /dev/null +++ b/src/main/webapp/WEB-INF/tags/oldBrowserWarning.tag @@ -0,0 +1,18 @@ +<%@ tag language="java" pageEncoding="UTF-8"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/views/connectionManagement/mainrunconnection.jsp b/src/main/webapp/WEB-INF/views/connectionManagement/mainrunconnection.jsp new file mode 100644 index 00000000..62d4e9fa --- /dev/null +++ b/src/main/webapp/WEB-INF/views/connectionManagement/mainrunconnection.jsp @@ -0,0 +1,138 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="iris" tagdir="/WEB-INF/tags" %> + +<%@ page session="false" %> + + + + + + + <spring:message code="management.page.title"/> + + + + + + + + + + + + + + + + + + + + +
+ +
+ + +
+

+ + + + to + + +

+
+
+ +
+
+ + +
+ +
+ + + +
+ + + + + + + + + +
+
+ +
+ + + +
+ + + + + + + + + +
+
+ +
+ + + +
+ + + + + + + + + + +
+
+ + + + + + + +
+ "/> + +
+
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/connectionManagement/mainrunconnections.jsp b/src/main/webapp/WEB-INF/views/connectionManagement/mainrunconnections.jsp new file mode 100644 index 00000000..ce287743 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/connectionManagement/mainrunconnections.jsp @@ -0,0 +1,87 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib uri="http://displaytag.sf.net" prefix="display" %> + + + + + + + <spring:message code="management.page.title"/> + + + + +
+
+
+

+ +

+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/general-error.jsp b/src/main/webapp/WEB-INF/views/general-error.jsp new file mode 100644 index 00000000..54bfb3ad --- /dev/null +++ b/src/main/webapp/WEB-INF/views/general-error.jsp @@ -0,0 +1,32 @@ +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@page import="java.io.PrintWriter" %> + +
+
+ + +
+
+
+ +
+

${exception}

+ +

+

+            <%
+                Exception e = (Exception) pageContext.findAttribute("exception");
+                if (e != null) {
+                    PrintWriter pw = new PrintWriter(out);
+                    e.printStackTrace(pw);
+                    pw.flush();
+                } else {
+                    pageContext.getOut().write("No Exception");
+                }
+            %>
+            
+

+
+
+
\ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/index.jsp b/src/main/webapp/WEB-INF/views/index.jsp new file mode 100644 index 00000000..92a163e1 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/index.jsp @@ -0,0 +1,20 @@ + +<%@ page language="java" contentType="text/html; charset=UTF-8"%> + +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + + + + + <spring:message code="index.page.title" /> + + + +
+ + +
+ + diff --git a/src/main/webapp/WEB-INF/views/iris-actions.jsp b/src/main/webapp/WEB-INF/views/iris-actions.jsp new file mode 100644 index 00000000..aca212ed --- /dev/null +++ b/src/main/webapp/WEB-INF/views/iris-actions.jsp @@ -0,0 +1,65 @@ + +<%@ page language="java" contentType="text/html; charset=UTF-8" %> + +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> + +
+
+

+ +

+
+
+
+
+

+ +

+
+
+ +
+ +
+

+ +

+ +

+ +

+
+
+ +
+ + +
+

+ +

+ +

+ +

+
+
+ + +
+
\ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/iris-explain.jsp b/src/main/webapp/WEB-INF/views/iris-explain.jsp new file mode 100644 index 00000000..3d2d9699 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/iris-explain.jsp @@ -0,0 +1,58 @@ + +<%@ page language="java" contentType="text/html; charset=UTF-8" %> + +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> + +
+
+

+ +

+
+
+

+ +

+

+ +

+ +

+ +

+ +

+ +

+ +

+ +

+ + +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+ +
+
\ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/login/login.jsp b/src/main/webapp/WEB-INF/views/login/login.jsp new file mode 100644 index 00000000..c667611a --- /dev/null +++ b/src/main/webapp/WEB-INF/views/login/login.jsp @@ -0,0 +1,70 @@ + +<%@ page language="java" contentType="text/html; charset=UTF-8" %> + +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="iris" tagdir="/WEB-INF/tags" %> + + + + + + <spring:message code="index.page.title"/> + + + + + +
+ +
+ +
+ +
+
+

+ +

+
+ +
+

+ +

+ +

+ +

+ +
+
+
+
+ "/> + "/> + "/> +
+
+
+
+ + + + +
+

+ +

+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/routing/triangle.jsp b/src/main/webapp/WEB-INF/views/routing/triangle.jsp new file mode 100644 index 00000000..0ad52bb0 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/routing/triangle.jsp @@ -0,0 +1,92 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> + +<%@ page session="false" %> + + + + + <spring:message code="triangle.page.title"/> + + + + +
+
+
+ +
+
+
+
Application is loading...
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/WEB-INF/views/seaportManagement/seaport.jsp b/src/main/webapp/WEB-INF/views/seaportManagement/seaport.jsp new file mode 100644 index 00000000..476f7174 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/seaportManagement/seaport.jsp @@ -0,0 +1,100 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="iris" tagdir="/WEB-INF/tags" %> + +<%@ page session="false" %> + + + + + + <spring:message code="management.page.title"/> + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+

+ +

+
+ +
+
+
+ +
+ + <%-- Seaport Information --%> +

+ +

+ + + +
+ + + + + + + + +
+ + + + + + + +
+ +
+ + + + +
+ "/> + + + +
+ +
+
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/seaportManagement/seaports.jsp b/src/main/webapp/WEB-INF/views/seaportManagement/seaports.jsp new file mode 100644 index 00000000..a74e2d9c --- /dev/null +++ b/src/main/webapp/WEB-INF/views/seaportManagement/seaports.jsp @@ -0,0 +1,71 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib uri="http://displaytag.sf.net" prefix="display" %> + + + + + + + <spring:message code="management.page.title"/> + + + + +
+ +
+
+

+ +

+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + " class="btn btn-primary"> + + + + + + + + + + +
+
+
+
+ + + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/staticAddressManagement/staticaddressForm.jsp b/src/main/webapp/WEB-INF/views/staticAddressManagement/staticaddressForm.jsp new file mode 100644 index 00000000..45c56eb2 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/staticAddressManagement/staticaddressForm.jsp @@ -0,0 +1,90 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="iris" tagdir="/WEB-INF/tags" %> +<%@ page session="false" %> + + + + + + + <spring:message code="management.page.title"/> + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+

+ +

+
+
+ +
+
+ +
+ + + + + +
+ + + + + + +
+ + + + + + +
+
+ + + + + +
+ "/> + + + + +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/staticAddressManagement/staticaddresses.jsp b/src/main/webapp/WEB-INF/views/staticAddressManagement/staticaddresses.jsp new file mode 100644 index 00000000..fa8e25fa --- /dev/null +++ b/src/main/webapp/WEB-INF/views/staticAddressManagement/staticaddresses.jsp @@ -0,0 +1,86 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="iris" tagdir="/WEB-INF/tags" %> +<%@ taglib uri="http://displaytag.sf.net" prefix="display" %> + + + + + + + <spring:message code="management.page.title"/> + + + + + + + + +
+
+
+

+ +

+
+
+ +
+
+
+ +
+ + + +
+ "/> + + + + + +
+
+
+
+
+
+ + +
+
+
+ + + + + + + + + + + + + + + + " + class="btn btn-primary"> + + + + + +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/terminalManagement/terminal.jsp b/src/main/webapp/WEB-INF/views/terminalManagement/terminal.jsp new file mode 100644 index 00000000..4b387e70 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/terminalManagement/terminal.jsp @@ -0,0 +1,106 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> +<%@ taglib prefix="iris" tagdir="/WEB-INF/tags" %> + +<%@ page session="false" %> + + + + + + + <spring:message code="management.page.title"/> + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+

+ +

+
+ +
+
+
+ +
+ + <%-- Terminal Information --%> +

+ +

+ + +
+ + + + + + + +
+ + + + + + +
+
+ + +
+ + + +
+ + + + + +
+
+ + + +
+ "/> + + + +
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/views/terminalManagement/terminals.jsp b/src/main/webapp/WEB-INF/views/terminalManagement/terminals.jsp new file mode 100644 index 00000000..9968b6e8 --- /dev/null +++ b/src/main/webapp/WEB-INF/views/terminalManagement/terminals.jsp @@ -0,0 +1,68 @@ +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@ taglib uri="http://displaytag.sf.net" prefix="display"%> + + + + + + + <spring:message code="management.page.title" /> + + + +
+
+
+

+ +

+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + " class="btn btn-primary"> + + + + + + + + + + +
+
+
+
+ + \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..cb6f1d20 --- /dev/null +++ b/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,108 @@ + + + + + + contextConfigLocation + classpath:application-context.xml + + + + + org.springframework.web.context.ContextLoaderListener + + + + + 480 + + IRISSESSION + + + + + + publicApi + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + classpath:/public-api-context.xml + + 1 + + + + publicApi + /api/* + + + + + webServlet + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + classpath:/web-api-context.xml + + 1 + + + + webServlet + /web/* + + + + encodingFilter + org.springframework.web.filter.CharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + + encodingFilter + /* + + + + + hiddenHttpMethodFilter + org.springframework.web.filter.HiddenHttpMethodFilter + + + hiddenHttpMethodFilter + webServlet + + + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + + springSecurityFilterChain + /* + + + + + sitemesh + com.opensymphony.sitemesh.webapp.SiteMeshFilter + + + + sitemesh + /web/* + + diff --git a/src/main/webapp/api-docs/css/reset.css b/src/main/webapp/api-docs/css/reset.css new file mode 100644 index 00000000..b2b07894 --- /dev/null +++ b/src/main/webapp/api-docs/css/reset.css @@ -0,0 +1,125 @@ +/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */ +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/src/main/webapp/api-docs/css/screen.css b/src/main/webapp/api-docs/css/screen.css new file mode 100644 index 00000000..1232eeea --- /dev/null +++ b/src/main/webapp/api-docs/css/screen.css @@ -0,0 +1,1256 @@ +/* Original style from softwaremaniacs.org (c) Ivan Sagalaev */ +.swagger-section pre code { + display: block; + padding: 0.5em; + background: #F0F0F0; +} +.swagger-section pre code, +.swagger-section pre .subst, +.swagger-section pre .tag .title, +.swagger-section pre .lisp .title, +.swagger-section pre .clojure .built_in, +.swagger-section pre .nginx .title { + color: black; +} +.swagger-section pre .string, +.swagger-section pre .title, +.swagger-section pre .constant, +.swagger-section pre .parent, +.swagger-section pre .tag .value, +.swagger-section pre .rules .value, +.swagger-section pre .rules .value .number, +.swagger-section pre .preprocessor, +.swagger-section pre .ruby .symbol, +.swagger-section pre .ruby .symbol .string, +.swagger-section pre .aggregate, +.swagger-section pre .template_tag, +.swagger-section pre .django .variable, +.swagger-section pre .smalltalk .class, +.swagger-section pre .addition, +.swagger-section pre .flow, +.swagger-section pre .stream, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .apache .cbracket, +.swagger-section pre .tex .command, +.swagger-section pre .tex .special, +.swagger-section pre .erlang_repl .function_or_atom, +.swagger-section pre .markdown .header { + color: #800; +} +.swagger-section pre .comment, +.swagger-section pre .annotation, +.swagger-section pre .template_comment, +.swagger-section pre .diff .header, +.swagger-section pre .chunk, +.swagger-section pre .markdown .blockquote { + color: #888; +} +.swagger-section pre .number, +.swagger-section pre .date, +.swagger-section pre .regexp, +.swagger-section pre .literal, +.swagger-section pre .smalltalk .symbol, +.swagger-section pre .smalltalk .char, +.swagger-section pre .go .constant, +.swagger-section pre .change, +.swagger-section pre .markdown .bullet, +.swagger-section pre .markdown .link_url { + color: #080; +} +.swagger-section pre .label, +.swagger-section pre .javadoc, +.swagger-section pre .ruby .string, +.swagger-section pre .decorator, +.swagger-section pre .filter .argument, +.swagger-section pre .localvars, +.swagger-section pre .array, +.swagger-section pre .attr_selector, +.swagger-section pre .important, +.swagger-section pre .pseudo, +.swagger-section pre .pi, +.swagger-section pre .doctype, +.swagger-section pre .deletion, +.swagger-section pre .envvar, +.swagger-section pre .shebang, +.swagger-section pre .apache .sqbracket, +.swagger-section pre .nginx .built_in, +.swagger-section pre .tex .formula, +.swagger-section pre .erlang_repl .reserved, +.swagger-section pre .prompt, +.swagger-section pre .markdown .link_label, +.swagger-section pre .vhdl .attribute, +.swagger-section pre .clojure .attribute, +.swagger-section pre .coffeescript .property { + color: #8888ff; +} +.swagger-section pre .keyword, +.swagger-section pre .id, +.swagger-section pre .phpdoc, +.swagger-section pre .title, +.swagger-section pre .built_in, +.swagger-section pre .aggregate, +.swagger-section pre .css .tag, +.swagger-section pre .javadoctag, +.swagger-section pre .phpdoc, +.swagger-section pre .yardoctag, +.swagger-section pre .smalltalk .class, +.swagger-section pre .winutils, +.swagger-section pre .bash .variable, +.swagger-section pre .apache .tag, +.swagger-section pre .go .typename, +.swagger-section pre .tex .command, +.swagger-section pre .markdown .strong, +.swagger-section pre .request, +.swagger-section pre .status { + font-weight: bold; +} +.swagger-section pre .markdown .emphasis { + font-style: italic; +} +.swagger-section pre .nginx .built_in { + font-weight: normal; +} +.swagger-section pre .coffeescript .javascript, +.swagger-section pre .javascript .xml, +.swagger-section pre .tex .formula, +.swagger-section pre .xml .javascript, +.swagger-section pre .xml .vbscript, +.swagger-section pre .xml .css, +.swagger-section pre .xml .cdata { + opacity: 0.5; +} +.swagger-section .swagger-ui-wrap { + line-height: 1; + font-family: "Droid Sans", sans-serif; + max-width: 960px; + margin-left: auto; + margin-right: auto; +} +.swagger-section .swagger-ui-wrap b, +.swagger-section .swagger-ui-wrap strong { + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap q, +.swagger-section .swagger-ui-wrap blockquote { + quotes: none; +} +.swagger-section .swagger-ui-wrap p { + line-height: 1.4em; + padding: 0 0 10px; + color: #333333; +} +.swagger-section .swagger-ui-wrap q:before, +.swagger-section .swagger-ui-wrap q:after, +.swagger-section .swagger-ui-wrap blockquote:before, +.swagger-section .swagger-ui-wrap blockquote:after { + content: none; +} +.swagger-section .swagger-ui-wrap .heading_with_menu h1, +.swagger-section .swagger-ui-wrap .heading_with_menu h2, +.swagger-section .swagger-ui-wrap .heading_with_menu h3, +.swagger-section .swagger-ui-wrap .heading_with_menu h4, +.swagger-section .swagger-ui-wrap .heading_with_menu h5, +.swagger-section .swagger-ui-wrap .heading_with_menu h6 { + display: block; + clear: none; + float: left; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + width: 60%; +} +.swagger-section .swagger-ui-wrap table { + border-collapse: collapse; + border-spacing: 0; +} +.swagger-section .swagger-ui-wrap table thead tr th { + padding: 5px; + font-size: 0.9em; + color: #666666; + border-bottom: 1px solid #999999; +} +.swagger-section .swagger-ui-wrap table tbody tr:last-child td { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap table tbody tr.offset { + background-color: #f0f0f0; +} +.swagger-section .swagger-ui-wrap table tbody tr td { + padding: 6px; + font-size: 0.9em; + border-bottom: 1px solid #cccccc; + vertical-align: top; + line-height: 1.3em; +} +.swagger-section .swagger-ui-wrap ol { + margin: 0px 0 10px; + padding: 0 0 0 18px; + list-style-type: decimal; +} +.swagger-section .swagger-ui-wrap ol li { + padding: 5px 0px; + font-size: 0.9em; + color: #333333; +} +.swagger-section .swagger-ui-wrap ol, +.swagger-section .swagger-ui-wrap ul { + list-style: none; +} +.swagger-section .swagger-ui-wrap h1 a, +.swagger-section .swagger-ui-wrap h2 a, +.swagger-section .swagger-ui-wrap h3 a, +.swagger-section .swagger-ui-wrap h4 a, +.swagger-section .swagger-ui-wrap h5 a, +.swagger-section .swagger-ui-wrap h6 a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap h1 a:hover, +.swagger-section .swagger-ui-wrap h2 a:hover, +.swagger-section .swagger-ui-wrap h3 a:hover, +.swagger-section .swagger-ui-wrap h4 a:hover, +.swagger-section .swagger-ui-wrap h5 a:hover, +.swagger-section .swagger-ui-wrap h6 a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap h1 span.divider, +.swagger-section .swagger-ui-wrap h2 span.divider, +.swagger-section .swagger-ui-wrap h3 span.divider, +.swagger-section .swagger-ui-wrap h4 span.divider, +.swagger-section .swagger-ui-wrap h5 span.divider, +.swagger-section .swagger-ui-wrap h6 span.divider { + color: #aaaaaa; +} +.swagger-section .swagger-ui-wrap a { + color: #547f00; +} +.swagger-section .swagger-ui-wrap a img { + border: none; +} +.swagger-section .swagger-ui-wrap article, +.swagger-section .swagger-ui-wrap aside, +.swagger-section .swagger-ui-wrap details, +.swagger-section .swagger-ui-wrap figcaption, +.swagger-section .swagger-ui-wrap figure, +.swagger-section .swagger-ui-wrap footer, +.swagger-section .swagger-ui-wrap header, +.swagger-section .swagger-ui-wrap hgroup, +.swagger-section .swagger-ui-wrap menu, +.swagger-section .swagger-ui-wrap nav, +.swagger-section .swagger-ui-wrap section, +.swagger-section .swagger-ui-wrap summary { + display: block; +} +.swagger-section .swagger-ui-wrap pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; +} +.swagger-section .swagger-ui-wrap pre code { + line-height: 1.6em; + background: none; +} +.swagger-section .swagger-ui-wrap .content > .content-type > div > label { + clear: both; + display: block; + color: #0F6AB4; + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap .content pre { + font-size: 12px; + margin-top: 5px; + padding: 5px; +} +.swagger-section .swagger-ui-wrap .icon-btn { + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .info_title { + padding-bottom: 10px; + font-weight: bold; + font-size: 25px; +} +.swagger-section .swagger-ui-wrap p.big, +.swagger-section .swagger-ui-wrap div.big p { + font-size: 1em; + margin-bottom: 10px; +} +.swagger-section .swagger-ui-wrap form.fullwidth ol li.string input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.url input, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.text textarea, +.swagger-section .swagger-ui-wrap form.fullwidth ol li.numeric input { + width: 500px !important; +} +.swagger-section .swagger-ui-wrap .info_license { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_tos { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .message-fail { + color: #cc0000; +} +.swagger-section .swagger-ui-wrap .info_url { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_email { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_name { + padding-bottom: 5px; +} +.swagger-section .swagger-ui-wrap .info_description { + padding-bottom: 10px; + font-size: 15px; +} +.swagger-section .swagger-ui-wrap .markdown ol li, +.swagger-section .swagger-ui-wrap .markdown ul li { + padding: 3px 0px; + line-height: 1.4em; + color: #333333; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input { + display: block; + padding: 4px; + width: auto; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.string input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.url input.title, +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.numeric input.title { + font-size: 1.3em; +} +.swagger-section .swagger-ui-wrap table.fullwidth { + width: 100%; +} +.swagger-section .swagger-ui-wrap .model-signature { + font-family: "Droid Sans", sans-serif; + font-size: 1em; + line-height: 1.5em; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a { + text-decoration: none; + color: #AAA; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-nav .selected { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap .model-signature .propType { + color: #5555aa; +} +.swagger-section .swagger-ui-wrap .model-signature pre:hover { + background-color: #ffffdd; +} +.swagger-section .swagger-ui-wrap .model-signature pre { + font-size: .85em; + line-height: 1.2em; + overflow: auto; + max-height: 200px; + cursor: pointer; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav { + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li:last-child { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap .model-signature ul.signature-nav li { + float: left; + margin: 0 5px 5px 0; + padding: 2px 5px 2px 0; + border-right: 1px solid #ddd; +} +.swagger-section .swagger-ui-wrap .model-signature .propOpt { + color: #555; +} +.swagger-section .swagger-ui-wrap .model-signature .snippet small { + font-size: 0.75em; +} +.swagger-section .swagger-ui-wrap .model-signature .propOptKey { + font-style: italic; +} +.swagger-section .swagger-ui-wrap .model-signature .description .strong { + font-weight: bold; + color: #000; + font-size: .9em; +} +.swagger-section .swagger-ui-wrap .model-signature .description div { + font-size: 0.9em; + line-height: 1.5em; + margin-left: 1em; +} +.swagger-section .swagger-ui-wrap .model-signature .description .stronger { + font-weight: bold; + color: #000; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper { + border-spacing: 0; + position: absolute; + background-color: #ffffff; + border: 1px solid #bbbbbb; + display: none; + font-size: 11px; + max-width: 400px; + line-height: 30px; + color: black; + padding: 5px; + margin-left: 10px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper th { + text-align: center; + background-color: #eeeeee; + border: 1px solid #bbbbbb; + font-size: 11px; + color: #666666; + font-weight: bold; + padding: 5px; + line-height: 15px; +} +.swagger-section .swagger-ui-wrap .model-signature .description .propWrap .optionsWrapper .optionName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .propName { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .model-signature .signature-container { + clear: both; +} +.swagger-section .swagger-ui-wrap .body-textarea { + width: 300px; + height: 100px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap .markdown p code, +.swagger-section .swagger-ui-wrap .markdown li code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #f0f0f0; + color: black; + padding: 1px 3px; +} +.swagger-section .swagger-ui-wrap .required { + font-weight: bold; +} +.swagger-section .swagger-ui-wrap input.parameter { + width: 300px; + border: 1px solid #aaa; +} +.swagger-section .swagger-ui-wrap h1 { + color: black; + font-size: 1.5em; + line-height: 1.3em; + padding: 10px 0 10px 0; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap .heading_with_menu { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap .heading_with_menu ul { + display: block; + clear: none; + float: right; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + margin-top: 10px; +} +.swagger-section .swagger-ui-wrap h2 { + color: black; + font-size: 1.3em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap h2 span.sub { + font-size: 0.7em; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap h2 span.sub a { + color: #777777; +} +.swagger-section .swagger-ui-wrap span.weak { + color: #666666; +} +.swagger-section .swagger-ui-wrap .message-success { + color: #89BF04; +} +.swagger-section .swagger-ui-wrap caption, +.swagger-section .swagger-ui-wrap th, +.swagger-section .swagger-ui-wrap td { + text-align: left; + font-weight: normal; + vertical-align: middle; +} +.swagger-section .swagger-ui-wrap .code { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.text textarea { + font-family: "Droid Sans", sans-serif; + height: 250px; + padding: 4px; + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.select select { + display: block; + clear: both; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean label { + display: block; + float: left; + clear: none; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.boolean input { + display: block; + float: left; + clear: none; + margin: 0 5px 0 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li.required label { + color: black; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label { + display: block; + clear: both; + width: auto; + padding: 0 0 3px; + color: #666666; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li label abbr { + padding-left: 3px; + color: #888888; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.inputs ol li p.inline-hints { + margin-left: 0; + font-style: italic; + font-size: 0.9em; + margin: 0; +} +.swagger-section .swagger-ui-wrap form.formtastic fieldset.buttons { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap span.blank, +.swagger-section .swagger-ui-wrap span.empty { + color: #888888; + font-style: italic; +} +.swagger-section .swagger-ui-wrap .markdown h3 { + color: #547f00; +} +.swagger-section .swagger-ui-wrap .markdown h4 { + color: #666666; +} +.swagger-section .swagger-ui-wrap .markdown pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + background-color: #fcf6db; + border: 1px solid #e5e0c6; + padding: 10px; + margin: 0 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown pre code { + line-height: 1.6em; +} +.swagger-section .swagger-ui-wrap div.gist { + margin: 20px 0 25px 0 !important; +} +.swagger-section .swagger-ui-wrap ul#resources { + font-family: "Droid Sans", sans-serif; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource { + border-bottom: 1px solid #dddddd; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading h2 a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading h2 a { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:hover div.heading ul.options li a, +.swagger-section .swagger-ui-wrap ul#resources li.resource.active div.heading ul.options li a { + color: #555555; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource:last-child { + border-bottom: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading { + border: 1px solid transparent; + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 14px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + border-right: 1px solid #dddddd; + color: #666666; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a { + color: #aaaaaa; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover { + text-decoration: underline; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading ul.options.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 { + color: #999999; + padding-left: 0; + display: block; + clear: none; + float: left; + font-family: "Droid Sans", sans-serif; + font-weight: bold; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a { + color: #999999; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource div.heading h2 a:hover { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0 0 10px; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading { + float: none; + clear: both; + overflow: hidden; + display: block; + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 { + display: block; + clear: none; + float: left; + width: auto; + margin: 0; + padding: 0; + line-height: 1.1em; + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path { + padding-left: 10px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a { + color: black; + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.path a:hover { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span.http_method a { + text-transform: uppercase; + text-decoration: none; + color: white; + display: inline-block; + width: 50px; + font-size: 0.7em; + text-align: center; + padding: 7px 0 4px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + -o-border-radius: 2px; + -ms-border-radius: 2px; + -khtml-border-radius: 2px; + border-radius: 2px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading h3 span { + margin: 0; + padding: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options { + overflow: hidden; + padding: 0; + display: block; + clear: none; + float: right; + margin: 6px 10px 0 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li { + float: left; + clear: none; + margin: 0; + padding: 2px 10px; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li a { + text-decoration: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.heading ul.options li.access { + color: black; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content { + border-top: none; + padding: 10px; + -moz-border-radius-bottomleft: 6px; + -webkit-border-bottom-left-radius: 6px; + -o-border-bottom-left-radius: 6px; + -ms-border-bottom-left-radius: 6px; + -khtml-border-bottom-left-radius: 6px; + border-bottom-left-radius: 6px; + -moz-border-radius-bottomright: 6px; + -webkit-border-bottom-right-radius: 6px; + -o-border-bottom-right-radius: 6px; + -ms-border-bottom-right-radius: 6px; + -khtml-border-bottom-right-radius: 6px; + border-bottom-right-radius: 6px; + margin: 0 0 20px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content h4 { + font-size: 1.1em; + margin: 0; + padding: 15px 0 5px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header { + float: none; + clear: both; + overflow: hidden; + display: block; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header a { + padding: 4px 0 0 10px; + display: inline-block; + font-size: 0.9em; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header input.submit { + display: block; + clear: none; + float: left; + padding: 6px 8px; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { + background-image: url('../images/throbber.gif'); + width: 128px; + height: 16px; + display: block; + clear: none; + float: right; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content form input[type='text'].error { + outline: 2px solid black; + outline-color: #cc0000; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.response div.block pre { + font-family: "Anonymous Pro", "Menlo", "Consolas", "Bitstream Vera Sans Mono", "Courier New", monospace; + padding: 10px; + font-size: 0.9em; + max-height: 400px; + overflow-y: auto; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading { + background-color: #f9f2e9; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading h3 span.http_method a { + background-color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0e0ca; + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li a { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content { + background-color: #faf5ee; + border: 1px solid #f0e0ca; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content h4 { + color: #c5862b; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #ffd20f; + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li a { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content { + background-color: #fcffcd; + border: 1px solid black; + border-color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content h4 { + color: #ffd20f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading { + background-color: #f5e8e8; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading h3 span.http_method a { + text-transform: uppercase; + background-color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #e8c6c7; + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li a { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + background-color: #f7eded; + border: 1px solid #e8c6c7; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content h4 { + color: #a41e22; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content div.sandbox_header a { + color: #c8787a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading { + background-color: #e7f6ec; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading h3 span.http_method a { + background-color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3e8d1; + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li a { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content { + background-color: #ebf7f0; + border: 1px solid #c3e8d1; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content h4 { + color: #10a54a; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content div.sandbox_header a { + color: #6fc992; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading { + background-color: #FCE9E3; + border: 1px solid #F5D5C3; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading h3 span.http_method a { + background-color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #f0cecb; + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li a { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content { + background-color: #faf0ef; + border: 1px solid #f0cecb; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content h4 { + color: #D38042; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content div.sandbox_header a { + color: #dcb67f; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading { + background-color: #e7f0f7; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading h3 span.http_method a { + background-color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li { + border-right: 1px solid #dddddd; + border-right-color: #c3d9ec; + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.heading ul.options li a { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content h4 { + color: #0f6ab4; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.options div.content div.sandbox_header a { + color: #6fa5d2; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.content, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.content { + border-top: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li:last-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.get div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.post div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.head div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.put div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.patch div.heading ul.options li.last, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation.delete div.heading ul.options li.last { + padding-right: 0; + border-right: none; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:hover, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a:active, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li a.active { + text-decoration: underline; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations ul.options li.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations:first-child, +.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations.first { + padding-left: 0; +} +.swagger-section .swagger-ui-wrap p#colophon { + margin: 0 15px 40px 15px; + padding: 10px 0; + font-size: 0.8em; + border-top: 1px solid #dddddd; + font-family: "Droid Sans", sans-serif; + color: #999999; + font-style: italic; +} +.swagger-section .swagger-ui-wrap p#colophon a { + text-decoration: none; + color: #547f00; +} +.swagger-section .swagger-ui-wrap h3 { + color: black; + font-size: 1.1em; + padding: 10px 0 10px 0; +} +.swagger-section .swagger-ui-wrap .markdown ol, +.swagger-section .swagger-ui-wrap .markdown ul { + font-family: "Droid Sans", sans-serif; + margin: 5px 0 10px; + padding: 0 0 0 18px; + list-style-type: disc; +} +.swagger-section .swagger-ui-wrap form.form_box { + background-color: #ebf3f9; + border: 1px solid #c3d9ec; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box label { + color: #0f6ab4 !important; +} +.swagger-section .swagger-ui-wrap form.form_box input[type=submit] { + display: block; + padding: 10px; +} +.swagger-section .swagger-ui-wrap form.form_box p.weak { + font-size: 0.8em; +} +.swagger-section .swagger-ui-wrap form.form_box p { + font-size: 0.9em; + padding: 0 0 15px; + color: #7e7b6d; +} +.swagger-section .swagger-ui-wrap form.form_box p a { + color: #646257; +} +.swagger-section .swagger-ui-wrap form.form_box p strong { + color: black; +} +.swagger-section .title { + font-style: bold; +} +.swagger-section .secondary_form { + display: none; +} +.swagger-section .main_image { + display: block; + margin-left: auto; + margin-right: auto; +} +.swagger-section .oauth_body { + margin-left: 100px; + margin-right: 100px; +} +.swagger-section .oauth_submit { + text-align: center; +} +.swagger-section .api-popup-dialog { + z-index: 10000; + position: absolute; + width: 500px; + background: #FFF; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + color: #777; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog .api-popup-title { + font-size: 24px; + padding: 10px 0; +} +.swagger-section .api-popup-dialog p.error-msg { + padding-left: 5px; + padding-bottom: 5px; +} +.swagger-section .api-popup-dialog button.api-popup-authbtn { + height: 30px; +} +.swagger-section .api-popup-dialog button.api-popup-cancel { + height: 30px; +} +.swagger-section .api-popup-scopes { + padding: 10px 20px; +} +.swagger-section .api-popup-scopes li { + padding: 5px 0; + line-height: 20px; +} +.swagger-section .api-popup-scopes .api-scope-desc { + padding-left: 20px; + font-style: italic; +} +.swagger-section .api-popup-scopes li input { + position: relative; + top: 2px; +} +.swagger-section .api-popup-actions { + padding-top: 10px; +} +.swagger-section .access { + float: right; +} +.swagger-section .auth { + float: right; +} +.swagger-section #api_information_panel { + position: absolute; + background: #FFF; + border: 1px solid #ccc; + border-radius: 5px; + display: none; + font-size: 13px; + max-width: 300px; + line-height: 30px; + color: black; + padding: 5px; +} +.swagger-section #api_information_panel p .api-msg-enabled { + color: green; +} +.swagger-section #api_information_panel p .api-msg-disabled { + color: red; +} +.swagger-section .api-ic { + height: 18px; + vertical-align: middle; + display: inline-block; + background: url(../images/explorer_icons.png) no-repeat; +} +.swagger-section .ic-info { + background-position: 0 0; + width: 18px; + margin-top: -7px; + margin-left: 4px; +} +.swagger-section .ic-warning { + background-position: -60px 0; + width: 18px; + margin-top: -7px; + margin-left: 4px; +} +.swagger-section .ic-error { + background-position: -30px 0; + width: 18px; + margin-top: -7px; + margin-left: 4px; +} +.swagger-section .ic-off { + background-position: -90px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section .ic-on { + background-position: -160px 0; + width: 58px; + margin-top: -4px; + cursor: pointer; +} +.swagger-section #header { + background-color: #00305d; + padding: 14px; +} +.swagger-section #header a#logo { + font-size: 1.5em; + font-weight: bold; + text-decoration: none; + background: transparent url(../images/logo_small.png) no-repeat left center; + padding: 20px 0 20px 40px; + color: white; +} +.swagger-section #header form#api_selector { + display: block; + clear: none; + float: right; +} +.swagger-section #header form#api_selector .input { + display: block; + clear: none; + float: left; + margin: 0 10px 0 0; +} +.swagger-section #header form#api_selector .input input#input_apiKey { + width: 200px; +} +.swagger-section #header form#api_selector .input input#input_baseUrl { + width: 400px; +} +.swagger-section #header form#api_selector .input a#explore { + display: block; + text-decoration: none; + font-weight: bold; + padding: 6px 8px; + font-size: 0.9em; + color: white; + background-color: #547f00; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -o-border-radius: 4px; + -ms-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} +.swagger-section #header form#api_selector .input a#explore:hover { + background-color: #547f00; +} +.swagger-section #header form#api_selector .input input { + font-size: 0.9em; + padding: 3px; + margin: 0; +} +.swagger-section #content_message { + margin: 10px 15px; + font-style: italic; + color: #999999; +} +.swagger-section #message-bar { + min-height: 30px; + text-align: center; + padding-top: 10px; +} diff --git a/src/main/webapp/api-docs/css/typography.css b/src/main/webapp/api-docs/css/typography.css new file mode 100644 index 00000000..27c3751a --- /dev/null +++ b/src/main/webapp/api-docs/css/typography.css @@ -0,0 +1,26 @@ +/* droid-sans-regular - latin */ +@font-face { + font-family: 'Droid Sans'; + font-style: normal; + font-weight: 400; + src: url('../fonts/droid-sans-v6-latin-regular.eot'); /* IE9 Compat Modes */ + src: local('Droid Sans'), local('DroidSans'), + url('../fonts/droid-sans-v6-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('../fonts/droid-sans-v6-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ + url('../fonts/droid-sans-v6-latin-regular.woff') format('woff'), /* Modern Browsers */ + url('../fonts/droid-sans-v6-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ + url('../fonts/droid-sans-v6-latin-regular.svg#DroidSans') format('svg'); /* Legacy iOS */ +} +/* droid-sans-700 - latin */ +@font-face { + font-family: 'Droid Sans'; + font-style: normal; + font-weight: 700; + src: url('../fonts/droid-sans-v6-latin-700.eot'); /* IE9 Compat Modes */ + src: local('Droid Sans Bold'), local('DroidSans-Bold'), + url('../fonts/droid-sans-v6-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('../fonts/droid-sans-v6-latin-700.woff2') format('woff2'), /* Super Modern Browsers */ + url('../fonts/droid-sans-v6-latin-700.woff') format('woff'), /* Modern Browsers */ + url('../fonts/droid-sans-v6-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */ + url('../fonts/droid-sans-v6-latin-700.svg#DroidSans') format('svg'); /* Legacy iOS */ +} diff --git a/src/main/webapp/api-docs/docs.html b/src/main/webapp/api-docs/docs.html new file mode 100644 index 00000000..9ed64d2f --- /dev/null +++ b/src/main/webapp/api-docs/docs.html @@ -0,0 +1,93 @@ + + + + Swagger UI + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+
+ + diff --git a/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.eot b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.eot new file mode 100644 index 00000000..2250b71a Binary files /dev/null and b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.eot differ diff --git a/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.svg b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.svg new file mode 100644 index 00000000..a54bbbbf --- /dev/null +++ b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.svg @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.ttf b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.ttf new file mode 100644 index 00000000..523cb92d Binary files /dev/null and b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.ttf differ diff --git a/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.woff b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.woff new file mode 100644 index 00000000..67e3e25f Binary files /dev/null and b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.woff differ diff --git a/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.woff2 b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.woff2 new file mode 100644 index 00000000..1e726a7c Binary files /dev/null and b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-700.woff2 differ diff --git a/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.eot b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.eot new file mode 100644 index 00000000..ac2698e8 Binary files /dev/null and b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.eot differ diff --git a/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.svg b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.svg new file mode 100644 index 00000000..d9f2a214 --- /dev/null +++ b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.svg @@ -0,0 +1,403 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.ttf b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.ttf new file mode 100644 index 00000000..76aede27 Binary files /dev/null and b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.ttf differ diff --git a/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.woff b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.woff new file mode 100644 index 00000000..abf19899 Binary files /dev/null and b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.woff differ diff --git a/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.woff2 b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.woff2 new file mode 100644 index 00000000..9f93f74c Binary files /dev/null and b/src/main/webapp/api-docs/fonts/droid-sans-v6-latin-regular.woff2 differ diff --git a/src/main/webapp/api-docs/images/explorer_icons.png b/src/main/webapp/api-docs/images/explorer_icons.png new file mode 100644 index 00000000..ed9d2fff Binary files /dev/null and b/src/main/webapp/api-docs/images/explorer_icons.png differ diff --git a/src/main/webapp/api-docs/images/logo_small.png b/src/main/webapp/api-docs/images/logo_small.png new file mode 100644 index 00000000..5496a655 Binary files /dev/null and b/src/main/webapp/api-docs/images/logo_small.png differ diff --git a/src/main/webapp/api-docs/images/pet_store_api.png b/src/main/webapp/api-docs/images/pet_store_api.png new file mode 100644 index 00000000..f9f9cd4a Binary files /dev/null and b/src/main/webapp/api-docs/images/pet_store_api.png differ diff --git a/src/main/webapp/api-docs/images/throbber.gif b/src/main/webapp/api-docs/images/throbber.gif new file mode 100644 index 00000000..06393889 Binary files /dev/null and b/src/main/webapp/api-docs/images/throbber.gif differ diff --git a/src/main/webapp/api-docs/images/wordnik_api.png b/src/main/webapp/api-docs/images/wordnik_api.png new file mode 100644 index 00000000..dca4f145 Binary files /dev/null and b/src/main/webapp/api-docs/images/wordnik_api.png differ diff --git a/src/main/webapp/api-docs/lib/backbone-min.js b/src/main/webapp/api-docs/lib/backbone-min.js new file mode 100644 index 00000000..a3f544be --- /dev/null +++ b/src/main/webapp/api-docs/lib/backbone-min.js @@ -0,0 +1,15 @@ +// Backbone.js 1.1.2 + +(function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}})(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('