forked from Qortal/qortal
Refactoring, new translations, cleaning up warnings.
Refactored to standard Maven layout: src/main/java src/main/resources src/test/java etc. New translation code that uses locale-specific ResourceBundles to load translations on demand. Reworked API error/exceptions code to a shorter, simpler @ApiErrors annotation. Processing of @ApiErrors annotations produces an example for each possible API error and includes API error string in HTTP response code, e.g. 400 INVALID_SIGNATURE Missing API error cases added to each API call. Translation of openAPI.json removed (for now). block-explorer.html and BIP39 wordlists now read as resources instead of direct from disk. Java compile warnings fixed. Some runtime warnings remain: WARNING: A provider api.resource.ApiDefinition registered in SERVER runtime does not implement any provider interfaces applicable in the SERVER runtime. WARNING: A provider api.resource.AnnotationPostProcessor registered in SERVER runtime does not implement any provider interfaces applicable in the SERVER runtime. WARN org.reflections.Reflections - given scan urls are empty. set urls in the configuration
This commit is contained in:
parent
aab6b69da1
commit
c4ed4b378c
26
.classpath
26
.classpath
@ -1,12 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<classpath>
|
<classpath>
|
||||||
<classpathentry kind="src" output="target/classes" path="src">
|
<classpathentry kind="src" output="target/classes" path="target/generated-sources/package-info">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="optional" value="true"/>
|
<attribute name="optional" value="true"/>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="src" output="target/classes" path="target/generated-sources/package-info">
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="optional" value="true"/>
|
<attribute name="optional" value="true"/>
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
@ -22,20 +28,11 @@
|
|||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="src" output="target/test-classes" path="tests">
|
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="optional" value="true"/>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
<classpathentry kind="src" path="target/generated-sources/annotations">
|
|
||||||
<attributes>
|
|
||||||
<attribute name="ignore_optional_problems" value="true"/>
|
|
||||||
<attribute name="optional" value="true"/>
|
|
||||||
<attribute name="maven.pomderived" value="true"/>
|
|
||||||
<attribute name="m2e-apt" value="true"/>
|
|
||||||
</attributes>
|
|
||||||
</classpathentry>
|
|
||||||
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
|
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="optional" value="true"/>
|
<attribute name="optional" value="true"/>
|
||||||
@ -44,5 +41,10 @@
|
|||||||
<attribute name="m2e-apt" value="true"/>
|
<attribute name="m2e-apt" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" path="target/generated-sources/annotations">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
<classpathentry kind="output" path="target/classes"/>
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
|
encoding//src/main/java=UTF-8
|
||||||
|
encoding//src/main/resources=UTF-8
|
||||||
|
encoding//src/test/java=UTF-8
|
||||||
encoding/<project>=UTF-8
|
encoding/<project>=UTF-8
|
||||||
encoding/src=UTF-8
|
encoding/src=UTF-8
|
||||||
encoding/tests=UTF-8
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
eclipse.preferences.version=1
|
eclipse.preferences.version=1
|
||||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||||
|
37
NOTES.md
37
NOTES.md
@ -1,37 +0,0 @@
|
|||||||
### General
|
|
||||||
|
|
||||||
- Reduce Qora2 down to core blockchain node with RESTful API access.
|
|
||||||
Other libraries can process name-storage data into websites, or provide web-based wallet UI, etc.
|
|
||||||
|
|
||||||
- Trying to reduce number of external dependencies where possible, e.g. avoiding heavy-weight ORM like Hive, Hibernate, etc.
|
|
||||||
|
|
||||||
- Trying to reduce duplicated code, especially across transactions with lots of serialisation and signature generation.
|
|
||||||
|
|
||||||
- Transaction signatures should really be generated after creating the transaction,
|
|
||||||
compared to the old style of generating signature first (and throw-away transaction in the process)
|
|
||||||
then using signature when creating actual transaction!
|
|
||||||
|
|
||||||
- Trying to keep most of the source structure, naming, code paths, etc. similar to old Qora to reduce brain load!
|
|
||||||
|
|
||||||
- More comments/javadoc
|
|
||||||
|
|
||||||
- More JUnit tests
|
|
||||||
|
|
||||||
### Differences due to switching from MapDB to HSQLDB
|
|
||||||
|
|
||||||
- We might need to maintain more mappings in the database, e.g. Qora address to/from public key,
|
|
||||||
as previously public key objects could be stored directly in MapDB.
|
|
||||||
|
|
||||||
- The new database tried to store the data in "rawest" form, i.e. raw ```byte[]``` signatures, not base58-encoded.
|
|
||||||
|
|
||||||
- The ```Transactions``` table contains ```creator``` column, which duplicates various child table columns,
|
|
||||||
e.g. ```PaymentTransactions.sender```,
|
|
||||||
so that all transactions by a specific Qora account can be quickly found without scanning all child tables.
|
|
||||||
|
|
||||||
- SQL is contained within repository classes repository.* (interfaces) and repository.hsqldb.* (implementations).
|
|
||||||
|
|
||||||
- We use transfer objects in data.*
|
|
||||||
|
|
||||||
- "Business logic" is left in qora.*
|
|
||||||
|
|
||||||
- Some MapDB-based objects had Java Map<> objects as their values. These will need to be unpacked into separate tables.
|
|
@ -1,104 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<localization>
|
|
||||||
<context locale="de">
|
|
||||||
<context path="Api">
|
|
||||||
<context path="ApiError">
|
|
||||||
<translation key="0" template="Unbekannter Fehler" />
|
|
||||||
<translation key="1" template="JSON Nachricht konnte nicht geparsed werden" />
|
|
||||||
<translation key="2" template="Guthaben ungenügend" />
|
|
||||||
<translation key="3" template="Feature wurde noch nicht veröffentlicht" />
|
|
||||||
|
|
||||||
<translation key="101" template="Ungültige Signatur" />
|
|
||||||
<translation key="102" template="Ungültige Adresse" />
|
|
||||||
<translation key="103" template="Ungültiger Seed" />
|
|
||||||
<translation key="104" template="Ungültiger Betrag" />
|
|
||||||
<translation key="105" template="Ungültige Gebühr" />
|
|
||||||
<translation key="106" template="Ungültiger Sender" />
|
|
||||||
<translation key="107" template="Ungültiger Empfänger" />
|
|
||||||
<translation key="108" template="Ungültige Namenslänge" />
|
|
||||||
<translation key="109" template="Ungültige Wertlänge" />
|
|
||||||
<translation key="110" template="Ungültiger Namensbesitzer" />
|
|
||||||
<translation key="111" template="Ungültiger Käufer" />
|
|
||||||
<translation key="112" template="Ungültiger Public Key" />
|
|
||||||
<translation key="113" template="Ungültige Optionen-Länge" />
|
|
||||||
<translation key="114" template="Ungültige Optionslänge" />
|
|
||||||
<translation key="115" template="Ungültige Daten" />
|
|
||||||
<translation key="116" template="Ungültige Datenlänge" />
|
|
||||||
<translation key="117" template="Ungültiger Update-Wert" />
|
|
||||||
<translation key="118" template="Der Schlüssel existiert bereits, Editieren ist deaktiviert" />
|
|
||||||
<translation key="119" template="Der Schlüssel existiert nicht" />
|
|
||||||
<translation key="120" template="Du kannst den Schlüssel '${key}' nicht löschen, wenn er der einzige ist" />
|
|
||||||
<translation key="121" template="fee less required" />
|
|
||||||
<translation key="122" template="Das Wallet muss synchronisiert werden" />
|
|
||||||
<translation key="123" template="Ungültige Netzwerkadresse" />
|
|
||||||
|
|
||||||
<translation key="201" template="Das Wallet existiert nicht" />
|
|
||||||
<translation key="202" template="Die Adresse existiert nicht im Wallet" />
|
|
||||||
<translation key="203" template="Das Wallet ist abgeschlossen" />
|
|
||||||
<translation key="204" template="Das Wallet existiert bereits" />
|
|
||||||
<translation key="205" template="Der Benutzer hat den API-Aufruf abgelehnt" />
|
|
||||||
|
|
||||||
<translation key="301" template="Der Block existiert nicht" />
|
|
||||||
<translation key="311" template="Die Transaktion existiert nicht" />
|
|
||||||
<translation key="304" template="Public Key wurde nicht gefunden" />
|
|
||||||
|
|
||||||
<translation key="401" template="Der Name existiert nicht" />
|
|
||||||
<translation key="402" template="Der Name existiert bereits" />
|
|
||||||
<translation key="403" template="Der Name steht bereits zum Verkauf" />
|
|
||||||
<translation key="404" template="Der Name muss aus Kleinbuchstaben bestehen" />
|
|
||||||
<translation key="410" template="Namensverkauf existiert nicht" />
|
|
||||||
<translation key="411" template="Der Käufer ist bereits Besitzer" />
|
|
||||||
|
|
||||||
<translation key="501" template="Die Abstimmung existiert nicht" />
|
|
||||||
<translation key="502" template="Die Abstimmung existiert bereits" />
|
|
||||||
<translation key="503" template="Nicht alle Optionen sind eindeutig" />
|
|
||||||
<translation key="504" template="Die option existiert nicht" />
|
|
||||||
<translation key="505" template="Bereits für diese Option abgestimmt" />
|
|
||||||
|
|
||||||
<translation key="601" template="Ungültige Asset ID" />
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<translation key="701" template="?NAME_NOT_REGISTERED?" />
|
|
||||||
<translation key="702" template="?NAME_FOR_SALE?" />
|
|
||||||
<translation key="703" template="?NAME_WITH_SPACE?" />
|
|
||||||
-->
|
|
||||||
|
|
||||||
<translation key="801" template="Ungültige Beschreibungslänge. Max. Länge ${MAX_LENGTH}" />
|
|
||||||
<translation key="802" template="Der Code ist leer" />
|
|
||||||
<translation key="803" template="Ungültige Datenlänge" />
|
|
||||||
<translation key="804" template="Ungültige Seiten" />
|
|
||||||
<translation key="805" template="Ungültige Typlänge" />
|
|
||||||
<translation key="806" template="Ungültige Tag-Länge" />
|
|
||||||
<translation key="809" template="Fehler in Creation Bytes" />
|
|
||||||
|
|
||||||
<translation key="901" template="invalid body it must not be empty" />
|
|
||||||
<translation key="902" template="Dieser Blog ist deaktiviert" />
|
|
||||||
<translation key="903" template="the creator address does not own the author name" />
|
|
||||||
<translation key="904" template="the data size is too large - currently only ${BATCH_TX_AMOUNT} arbitrary transactions are allowed at once!" />
|
|
||||||
<translation key="905" template="transaction with this signature contains no entries!" />
|
|
||||||
<translation key="906" template="this blog is empty" />
|
|
||||||
<translation key="907" template="the attribute postid is empty! this is the signature of the post you want to comment" />
|
|
||||||
<translation key="908" template="for the given postid no blogpost to comment was found" />
|
|
||||||
<translation key="909" template="commenting is for this blog disabled" />
|
|
||||||
<translation key="910" template="for the given signature no comment was found" />
|
|
||||||
<translation key="911" template="invalid comment owner" />
|
|
||||||
|
|
||||||
<translation key="1001" template="the Message format is not hex - correct the text or use isTextMessage = true" />
|
|
||||||
<translation key="1002" template="The message attribute is missing or content is blank" />
|
|
||||||
<translation key="1003" template="The recipient has not yet performed any action in the blockchain.\nYou can't send an encrypted message to him." />
|
|
||||||
<translation key="1004" template="Message size exceeded!" />
|
|
||||||
</context>
|
|
||||||
|
|
||||||
<context path="ApiClient">
|
|
||||||
<translation key="invalid command" template="Ungültiger Befehl!\nGib 'help all' ein, um eine Liste aller gültigen Befehle zu erhalten." />
|
|
||||||
<translation key="error footer" template="Gib 'help all' ein, um eine Liste aller gültigen Befehle zu erhalten." />
|
|
||||||
|
|
||||||
<translation key="help: success responses" template="Antwort bei Erfolg:" />
|
|
||||||
<translation key="help: failure responses" template="Antwort bei Misserfolg:" />
|
|
||||||
</context>
|
|
||||||
|
|
||||||
</context>
|
|
||||||
|
|
||||||
</context>
|
|
||||||
|
|
||||||
</localization>
|
|
@ -1,108 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<localization>
|
|
||||||
<context locale="en">
|
|
||||||
<context path="Api">
|
|
||||||
<context path="ApiError">
|
|
||||||
<translation key="0" template="unknown error" />
|
|
||||||
<translation key="1" template="failed to parse json message" />
|
|
||||||
<translation key="2" template="not enough balance" />
|
|
||||||
<translation key="3" template="that feature is not yet released" />
|
|
||||||
|
|
||||||
<translation key="101" template="invalid signature" />
|
|
||||||
<translation key="102" template="invalid address" />
|
|
||||||
<translation key="103" template="invalid seed" />
|
|
||||||
<translation key="104" template="invalid amount" />
|
|
||||||
<translation key="105" template="invalid fee" />
|
|
||||||
<translation key="106" template="invalid sender" />
|
|
||||||
<translation key="107" template="invalid recipient" />
|
|
||||||
<translation key="108" template="invalid name length" />
|
|
||||||
<translation key="109" template="invalid value length" />
|
|
||||||
<translation key="110" template="invalid name owner" />
|
|
||||||
<translation key="111" template="invalid buyer" />
|
|
||||||
<translation key="112" template="invalid public key" />
|
|
||||||
<translation key="113" template="invalid options length" />
|
|
||||||
<translation key="114" template="invalid option length" />
|
|
||||||
<translation key="115" template="invalid data" />
|
|
||||||
<translation key="116" template="invalid data length" />
|
|
||||||
<translation key="117" template="invalid update value" />
|
|
||||||
<translation key="118" template="key already exists, edit is false" />
|
|
||||||
<translation key="119" template="the key does not exist" />
|
|
||||||
<translation key="120" template="you can't delete the key '${key}' if it is the only key" />
|
|
||||||
<translation key="121" template="fee less required" />
|
|
||||||
<translation key="122" template="wallet needs to be synchronized" />
|
|
||||||
<translation key="123" template="invalid network address" />
|
|
||||||
|
|
||||||
<translation key="201" template="wallet does not exist" />
|
|
||||||
<translation key="202" template="address does not exist in wallet" />
|
|
||||||
<translation key="203" template="wallet is locked" />
|
|
||||||
<translation key="204" template="wallet already exists" />
|
|
||||||
<translation key="205" template="user denied api call" />
|
|
||||||
|
|
||||||
<translation key="301" template="block does not exist" />
|
|
||||||
<translation key="311" template="transactions does not exist" />
|
|
||||||
<translation key="304" template="public key not found" />
|
|
||||||
|
|
||||||
<translation key="401" template="name does not exist" />
|
|
||||||
<translation key="402" template="name already exists" />
|
|
||||||
<translation key="403" template="name already for sale" />
|
|
||||||
<translation key="404" template="name must be lower case" />
|
|
||||||
<translation key="410" template="namesale does not exist" />
|
|
||||||
<translation key="411" template="buyer is already owner" />
|
|
||||||
|
|
||||||
<translation key="501" template="poll does not exist" />
|
|
||||||
<translation key="502" template="poll already exists" />
|
|
||||||
<translation key="503" template="not all options are unique" />
|
|
||||||
<translation key="504" template="option does not exist" />
|
|
||||||
<translation key="505" template="already voted for that option" />
|
|
||||||
|
|
||||||
<translation key="601" template="invalid asset id" />
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<translation key="701" template="?NAME_NOT_REGISTERED?" />
|
|
||||||
<translation key="702" template="?NAME_FOR_SALE?" />
|
|
||||||
<translation key="703" template="?NAME_WITH_SPACE?" />
|
|
||||||
-->
|
|
||||||
|
|
||||||
<translation key="801" template="invalid description length. max length ${MAX_LENGTH}" />
|
|
||||||
<translation key="802" template="code is empty" />
|
|
||||||
<translation key="803" template="invalid data length" />
|
|
||||||
<translation key="804" template="invalid pages" />
|
|
||||||
<translation key="805" template="invalid type length" />
|
|
||||||
<translation key="806" template="invalid tags length" />
|
|
||||||
<translation key="809" template="error in creation bytes" />
|
|
||||||
|
|
||||||
<translation key="901" template="invalid body it must not be empty" />
|
|
||||||
<translation key="902" template="this blog is disabled" />
|
|
||||||
<translation key="903" template="the creator address does not own the author name" />
|
|
||||||
<translation key="904" template="the data size is too large - currently only ${BATCH_TX_AMOUNT} arbitrary transactions are allowed at once!" />
|
|
||||||
<translation key="905" template="transaction with this signature contains no entries!" />
|
|
||||||
<translation key="906" template="this blog is empty" />
|
|
||||||
<translation key="907" template="the attribute postid is empty! this is the signature of the post you want to comment" />
|
|
||||||
<translation key="908" template="for the given postid no blogpost to comment was found" />
|
|
||||||
<translation key="909" template="commenting is for this blog disabled" />
|
|
||||||
<translation key="910" template="for the given signature no comment was found" />
|
|
||||||
<translation key="911" template="invalid comment owner" />
|
|
||||||
|
|
||||||
<translation key="1001" template="the Message format is not hex - correct the text or use isTextMessage = true" />
|
|
||||||
<translation key="1002" template="The message attribute is missing or content is blank" />
|
|
||||||
<translation key="1003" template="The recipient has not yet performed any action in the blockchain.\nYou can't send an encrypted message to him." />
|
|
||||||
<translation key="1004" template="Message size exceeded!" />
|
|
||||||
</context>
|
|
||||||
|
|
||||||
<context path="ApiClient">
|
|
||||||
<translation key="invalid command" template="Invalid command! \nType 'help all' to get a list of commands." />
|
|
||||||
<translation key="error footer" template="Type 'help all' to get a list of commands." />
|
|
||||||
|
|
||||||
<translation key="API error response" template="(API error: ${ERROR_CODE}) ${DESCRIPTION}" />
|
|
||||||
|
|
||||||
<translation key="error: with body" template="HTTP Status ${STATUS}: ${BODY}" />
|
|
||||||
<translation key="error: without body" template="HTTP Status ${STATUS}" />
|
|
||||||
<translation key="help: success responses" template="On success returns:" />
|
|
||||||
<translation key="help: failure responses" template="On failure returns:" />
|
|
||||||
</context>
|
|
||||||
|
|
||||||
</context>
|
|
||||||
|
|
||||||
</context>
|
|
||||||
|
|
||||||
</localization>
|
|
11
pom.xml
11
pom.xml
@ -17,8 +17,8 @@
|
|||||||
<swagger-ui.version>3.19.0</swagger-ui.version>
|
<swagger-ui.version>3.19.0</swagger-ui.version>
|
||||||
</properties>
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<sourceDirectory>src</sourceDirectory>
|
<sourceDirectory>src/main/java</sourceDirectory>
|
||||||
<testSourceDirectory>tests</testSourceDirectory>
|
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
@ -119,14 +119,13 @@
|
|||||||
<packages>
|
<packages>
|
||||||
<package>
|
<package>
|
||||||
<pattern>data.**</pattern>
|
<pattern>data.**</pattern>
|
||||||
<template>${project.basedir}/src/data/package-info.java</template>
|
<template>${project.build.sourceDirectory}/data/package-info.java</template>
|
||||||
</package>
|
</package>
|
||||||
<package>
|
<package>
|
||||||
<pattern>api.models**</pattern>
|
<pattern>api.models**</pattern>
|
||||||
<template>${project.basedir}/src/data/package-info.java</template>
|
<template>${project.build.sourceDirectory}/data/package-info.java</template>
|
||||||
</package>
|
</package>
|
||||||
</packages>
|
</packages>
|
||||||
<sourceDirectory>${project.basedir}/src</sourceDirectory>
|
|
||||||
<outputDirectory>${project.build.directory}/generated-sources/package-info</outputDirectory>
|
<outputDirectory>${project.build.directory}/generated-sources/package-info</outputDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
@ -391,4 +390,4 @@
|
|||||||
<version>${bouncycastle.version}</version>
|
<version>${bouncycastle.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
@ -1,239 +0,0 @@
|
|||||||
package api;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
|
||||||
import globalization.ContextPaths;
|
|
||||||
import globalization.Translator;
|
|
||||||
import io.swagger.v3.core.converter.ModelConverters;
|
|
||||||
import io.swagger.v3.jaxrs2.Reader;
|
|
||||||
import io.swagger.v3.jaxrs2.ReaderListener;
|
|
||||||
import io.swagger.v3.oas.models.Components;
|
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
|
||||||
import io.swagger.v3.oas.models.Operation;
|
|
||||||
import io.swagger.v3.oas.models.PathItem;
|
|
||||||
import io.swagger.v3.oas.models.examples.Example;
|
|
||||||
import io.swagger.v3.oas.models.info.Info;
|
|
||||||
import io.swagger.v3.oas.models.media.Content;
|
|
||||||
import io.swagger.v3.oas.models.media.MediaType;
|
|
||||||
import io.swagger.v3.oas.models.media.Schema;
|
|
||||||
import io.swagger.v3.oas.models.parameters.Parameter;
|
|
||||||
import io.swagger.v3.oas.models.responses.ApiResponse;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
|
|
||||||
|
|
||||||
public class AnnotationPostProcessor implements ReaderListener {
|
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(AnnotationPostProcessor.class);
|
|
||||||
|
|
||||||
private class ContextInformation {
|
|
||||||
public String path;
|
|
||||||
public Map<String, String> keys;
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Translator translator;
|
|
||||||
private final ApiErrorFactory apiErrorFactory;
|
|
||||||
|
|
||||||
public AnnotationPostProcessor() {
|
|
||||||
this(Translator.getInstance(), ApiErrorFactory.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
public AnnotationPostProcessor(Translator translator, ApiErrorFactory apiErrorFactory) {
|
|
||||||
this.translator = translator;
|
|
||||||
this.apiErrorFactory = apiErrorFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeScan(Reader reader, OpenAPI openAPI) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterScan(Reader reader, OpenAPI openAPI) {
|
|
||||||
// Populate Components section with reusable parameters, like "limit" and "offset"
|
|
||||||
// We take the reusable parameters from AdminResource.globalParameters path "/admin/unused"
|
|
||||||
Components components = openAPI.getComponents();
|
|
||||||
PathItem globalParametersPathItem = openAPI.getPaths().get("/admin/unused");
|
|
||||||
if (globalParametersPathItem != null) {
|
|
||||||
for (Parameter parameter : globalParametersPathItem.getGet().getParameters())
|
|
||||||
components.addParameters(parameter.getName(), parameter);
|
|
||||||
openAPI.getPaths().remove("/admin/unused");
|
|
||||||
}
|
|
||||||
|
|
||||||
// use context path and keys from "x-translation" extension annotations
|
|
||||||
// to translate supported annotations and finally remove "x-translation" extensions
|
|
||||||
Info resourceInfo = openAPI.getInfo();
|
|
||||||
ContextInformation resourceContext = getContextInformation(openAPI.getExtensions());
|
|
||||||
removeTranslationAnnotations(openAPI.getExtensions());
|
|
||||||
TranslateProperties(Constants.TRANSLATABLE_INFO_PROPERTIES, resourceContext, resourceInfo);
|
|
||||||
|
|
||||||
for (Map.Entry<String, PathItem> pathEntry : openAPI.getPaths().entrySet())
|
|
||||||
{
|
|
||||||
PathItem pathItem = pathEntry.getValue();
|
|
||||||
ContextInformation pathContext = getContextInformation(pathItem.getExtensions(), resourceContext);
|
|
||||||
removeTranslationAnnotations(pathItem.getExtensions());
|
|
||||||
TranslateProperties(Constants.TRANSLATABLE_PATH_ITEM_PROPERTIES, pathContext, pathItem);
|
|
||||||
|
|
||||||
for (Operation operation : pathItem.readOperations()) {
|
|
||||||
ContextInformation operationContext = getContextInformation(operation.getExtensions(), pathContext);
|
|
||||||
removeTranslationAnnotations(operation.getExtensions());
|
|
||||||
TranslateProperties(Constants.TRANSLATABLE_OPERATION_PROPERTIES, operationContext, operation);
|
|
||||||
|
|
||||||
addApiErrorResponses(operation);
|
|
||||||
removeApiErrorsAnnotations(operation.getExtensions());
|
|
||||||
|
|
||||||
for (Map.Entry<String, ApiResponse> responseEntry : operation.getResponses().entrySet()) {
|
|
||||||
ApiResponse response = responseEntry.getValue();
|
|
||||||
ContextInformation responseContext = getContextInformation(response.getExtensions(), operationContext);
|
|
||||||
removeTranslationAnnotations(response.getExtensions());
|
|
||||||
TranslateProperties(Constants.TRANSLATABLE_API_RESPONSE_PROPERTIES, responseContext, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addApiErrorResponses(Operation operation) {
|
|
||||||
List<ApiError> apiErrors = getApiErrors(operation.getExtensions());
|
|
||||||
if(apiErrors != null) {
|
|
||||||
for(ApiError apiError : apiErrors) {
|
|
||||||
String statusCode = Integer.toString(apiError.getStatus());
|
|
||||||
ApiResponse apiResponse = operation.getResponses().get(statusCode);
|
|
||||||
if(apiResponse == null) {
|
|
||||||
Schema errorMessageSchema = ModelConverters.getInstance().readAllAsResolvedSchema(ApiErrorMessage.class).schema;
|
|
||||||
MediaType mediaType = new MediaType().schema(errorMessageSchema);
|
|
||||||
Content content = new Content().addMediaType(javax.ws.rs.core.MediaType.APPLICATION_JSON, mediaType);
|
|
||||||
apiResponse = new ApiResponse().content(content);
|
|
||||||
operation.getResponses().addApiResponse(statusCode, apiResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
int apiErrorCode = apiError.getCode();
|
|
||||||
ApiErrorMessage apiErrorMessage = new ApiErrorMessage(apiErrorCode, this.apiErrorFactory.getErrorMessage(apiError));
|
|
||||||
Example example = new Example().value(apiErrorMessage);
|
|
||||||
|
|
||||||
// XXX: addExamples(..) is not working in Swagger 2.0.4. This bug is referenced in https://github.com/swagger-api/swagger-ui/issues/2651
|
|
||||||
// Replace the call to .setExample(..) by .addExamples(..) when the bug is fixed.
|
|
||||||
apiResponse.getContent().get(javax.ws.rs.core.MediaType.APPLICATION_JSON).setExample(example);
|
|
||||||
//apiResponse.getContent().get(javax.ws.rs.core.MediaType.APPLICATION_JSON).addExamples(Integer.toString(apiErrorCode), example);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> void TranslateProperties(List<TranslatableProperty<T>> translatableProperties, ContextInformation context, T item) {
|
|
||||||
if(context.keys != null) {
|
|
||||||
Map<String, String> keys = context.keys;
|
|
||||||
for(TranslatableProperty<T> prop : translatableProperties) {
|
|
||||||
String key = keys.get(prop.keyName());
|
|
||||||
if(key != null) {
|
|
||||||
String originalValue = prop.getValue(item);
|
|
||||||
// XXX: use browser locale instead default?
|
|
||||||
String translation = translator.translate(context.path, key, originalValue);
|
|
||||||
prop.setValue(item, translation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ApiError> getApiErrors(Map<String, Object> extensions) {
|
|
||||||
if(extensions == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
List<String> apiErrorStrings = new ArrayList();
|
|
||||||
try {
|
|
||||||
ArrayNode apiErrorsNode = (ArrayNode)extensions.get("x-" + Constants.API_ERRORS_EXTENSION_NAME);
|
|
||||||
if(apiErrorsNode == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
for(int i = 0; i < apiErrorsNode.size(); i++) {
|
|
||||||
String errorString = apiErrorsNode.get(i).asText();
|
|
||||||
apiErrorStrings.add(errorString);
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
|
||||||
// TODO: error logging
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ApiError> result = new ArrayList<>();
|
|
||||||
for(String apiErrorString : apiErrorStrings) {
|
|
||||||
ApiError apiError = null;
|
|
||||||
try {
|
|
||||||
apiError = ApiError.valueOf(apiErrorString);
|
|
||||||
} catch(IllegalArgumentException e) {
|
|
||||||
try {
|
|
||||||
int errorCodeInt = Integer.parseInt(apiErrorString);
|
|
||||||
apiError = ApiError.fromCode(errorCodeInt);
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(apiError == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
result.add(apiError);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContextInformation getContextInformation(Map<String, Object> extensions) {
|
|
||||||
return getContextInformation(extensions, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ContextInformation getContextInformation(Map<String, Object> extensions, ContextInformation base) {
|
|
||||||
if(extensions != null) {
|
|
||||||
Map<String, Object> translationDefinitions = (Map<String, Object>)extensions.get("x-" + Constants.TRANSLATION_EXTENSION_NAME);
|
|
||||||
if(translationDefinitions != null) {
|
|
||||||
ContextInformation result = new ContextInformation();
|
|
||||||
result.path = combinePaths(base, (String)translationDefinitions.get(Constants.TRANSLATION_PATH_EXTENSION_NAME));
|
|
||||||
result.keys = getTranslationKeys(translationDefinitions);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(base != null) {
|
|
||||||
ContextInformation result = new ContextInformation();
|
|
||||||
result.path = base.path;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeApiErrorsAnnotations(Map<String, Object> extensions) {
|
|
||||||
String extensionName = Constants.API_ERRORS_EXTENSION_NAME;
|
|
||||||
removeExtension(extensions, extensionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeTranslationAnnotations(Map<String, Object> extensions) {
|
|
||||||
String extensionName = Constants.TRANSLATION_EXTENSION_NAME;
|
|
||||||
removeExtension(extensions, extensionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeExtension(Map<String, Object> extensions, String extensionName) {
|
|
||||||
if(extensions == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
extensions.remove("x-" + extensionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> getTranslationKeys(Map<String, Object> translationDefinitions) {
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
|
|
||||||
for(TranslatableProperty prop : Constants.TRANSLATABLE_INFO_PROPERTIES) {
|
|
||||||
String key = (String)translationDefinitions.get(prop.keyName());
|
|
||||||
if(key != null)
|
|
||||||
result.put(prop.keyName(), key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String combinePaths(ContextInformation base, String path) {
|
|
||||||
String basePath = (base != null) ? base.path : null;
|
|
||||||
return ContextPaths.combinePaths(basePath, path);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,340 +0,0 @@
|
|||||||
package api;
|
|
||||||
|
|
||||||
import globalization.ContextPaths;
|
|
||||||
import globalization.Translator;
|
|
||||||
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
|
||||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
|
||||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.PUT;
|
|
||||||
import javax.ws.rs.PATCH;
|
|
||||||
import javax.ws.rs.DELETE;
|
|
||||||
import javax.ws.rs.HttpMethod;
|
|
||||||
import javax.ws.rs.client.Client;
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
import javax.ws.rs.client.Invocation;
|
|
||||||
import javax.ws.rs.client.WebTarget;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
|
|
||||||
import settings.Settings;
|
|
||||||
|
|
||||||
public class ApiClient {
|
|
||||||
|
|
||||||
private class HelpInfo {
|
|
||||||
|
|
||||||
public final Pattern pattern;
|
|
||||||
public final String fullPath;
|
|
||||||
public final String description;
|
|
||||||
public final List<String> success;
|
|
||||||
public final List<String> errors;
|
|
||||||
|
|
||||||
public HelpInfo(Pattern pattern, String fullPath, String description, List<String> success, List<String> errors) {
|
|
||||||
this.pattern = pattern;
|
|
||||||
this.fullPath = fullPath;
|
|
||||||
this.description = description;
|
|
||||||
this.success = success;
|
|
||||||
this.errors = errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String TRANSLATION_CONTEXT_PATH = "/Api/ApiClient";
|
|
||||||
|
|
||||||
private static final Pattern COMMAND_PATTERN = Pattern.compile("^ *(?<method>GET|POST|PUT|PATCH|DELETE) *(?<path>.*)$");
|
|
||||||
private static final Pattern HELP_COMMAND_PATTERN = Pattern.compile("^ *help *(?<command>.*)$", Pattern.CASE_INSENSITIVE);
|
|
||||||
private static final List<Class<? extends Annotation>> HTTP_METHOD_ANNOTATIONS = Arrays.asList(
|
|
||||||
GET.class,
|
|
||||||
POST.class,
|
|
||||||
PUT.class,
|
|
||||||
PATCH.class,
|
|
||||||
DELETE.class
|
|
||||||
);
|
|
||||||
|
|
||||||
private final Translator translator;
|
|
||||||
ApiService apiService;
|
|
||||||
List<HelpInfo> helpInfos;
|
|
||||||
|
|
||||||
public ApiClient(ApiService apiService, Translator translator) {
|
|
||||||
this.apiService = apiService;
|
|
||||||
this.translator = translator;
|
|
||||||
this.helpInfos = getHelpInfos(apiService.getResources());
|
|
||||||
}
|
|
||||||
|
|
||||||
//XXX: replace singleton pattern by dependency injection?
|
|
||||||
private static ApiClient instance;
|
|
||||||
|
|
||||||
public static ApiClient getInstance() {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new ApiClient(ApiService.getInstance(), Translator.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<HelpInfo> getHelpInfos(Iterable<Class<?>> resources) {
|
|
||||||
List<HelpInfo> result = new ArrayList<>();
|
|
||||||
|
|
||||||
// scan each resource class
|
|
||||||
for (Class<?> resource : resources) {
|
|
||||||
if (OpenApiResource.class.isAssignableFrom(resource)) {
|
|
||||||
continue; // ignore swagger resources
|
|
||||||
}
|
|
||||||
Path resourcePath = resource.getDeclaredAnnotation(Path.class);
|
|
||||||
if (resourcePath == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String resourcePathString = resourcePath.value();
|
|
||||||
|
|
||||||
// get translation context path for resource
|
|
||||||
String resourceContextPath = "/";
|
|
||||||
OpenAPIDefinition openAPIDefinition = resource.getDeclaredAnnotation(OpenAPIDefinition.class);
|
|
||||||
if(openAPIDefinition != null)
|
|
||||||
resourceContextPath = getContextPath(openAPIDefinition.extensions());
|
|
||||||
|
|
||||||
// scan each method
|
|
||||||
for (Method method : resource.getDeclaredMethods()) {
|
|
||||||
Operation operationAnnotation = method.getAnnotation(Operation.class);
|
|
||||||
if (operationAnnotation == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
String description = operationAnnotation.description();
|
|
||||||
|
|
||||||
// translate
|
|
||||||
String operationContextPath = ContextPaths.combinePaths(resourceContextPath, getContextPath(operationAnnotation.extensions()));
|
|
||||||
String operationDescriptionKey = getDescriptionTranslationKey(operationAnnotation.extensions());
|
|
||||||
if(operationDescriptionKey != null)
|
|
||||||
description = translator.translate(operationContextPath, operationDescriptionKey, description);
|
|
||||||
|
|
||||||
// extract responses
|
|
||||||
ArrayList success = new ArrayList();
|
|
||||||
ArrayList errors = new ArrayList();
|
|
||||||
for(ApiResponse response : operationAnnotation.responses()) {
|
|
||||||
String responseDescription = response.description();
|
|
||||||
if(StringUtils.isBlank(responseDescription))
|
|
||||||
continue; // ignore responses without description
|
|
||||||
|
|
||||||
// translate
|
|
||||||
String responseContextPath = ContextPaths.combinePaths(operationContextPath, getContextPath(response.extensions()));
|
|
||||||
String responseDescriptionKey = getDescriptionTranslationKey(response.extensions());
|
|
||||||
if(responseDescriptionKey != null)
|
|
||||||
responseDescription = translator.translate(responseContextPath, responseDescriptionKey, responseDescription);
|
|
||||||
|
|
||||||
String apiErrorCode = getApiErrorCode(response.extensions());
|
|
||||||
if(apiErrorCode != null) {
|
|
||||||
responseDescription = translator.translate(TRANSLATION_CONTEXT_PATH, "API error response", "(API error: ${ERROR_CODE}) ${DESCRIPTION}",
|
|
||||||
new AbstractMap.SimpleEntry<>("ERROR_CODE", apiErrorCode),
|
|
||||||
new AbstractMap.SimpleEntry<>("DESCRIPTION", responseDescription)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// try to identify response type by status code
|
|
||||||
int responseCode = Integer.parseInt(response.responseCode());
|
|
||||||
if(responseCode >= 400) {
|
|
||||||
errors.add(responseDescription);
|
|
||||||
} else {
|
|
||||||
success.add(responseDescription);
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
// try to identify response type by content
|
|
||||||
if(response.content().length > 0) {
|
|
||||||
Content content = response.content()[0];
|
|
||||||
Class<?> implementation = content.schema().implementation();
|
|
||||||
if(implementation != null && ApiErrorMessage.class.isAssignableFrom(implementation)) {
|
|
||||||
errors.add(responseDescription);
|
|
||||||
} else {
|
|
||||||
success.add(responseDescription);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
success.add(responseDescription);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Path methodPath = method.getDeclaredAnnotation(Path.class);
|
|
||||||
String methodPathString = (methodPath != null) ? methodPath.value() : "";
|
|
||||||
|
|
||||||
// scan for each potential http method
|
|
||||||
for (Class<? extends Annotation> restMethodAnnotation : HTTP_METHOD_ANNOTATIONS) {
|
|
||||||
Annotation annotation = method.getDeclaredAnnotation(restMethodAnnotation);
|
|
||||||
if (annotation == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpMethod httpMethod = annotation.annotationType().getDeclaredAnnotation(HttpMethod.class);
|
|
||||||
String httpMethodString = httpMethod.value();
|
|
||||||
|
|
||||||
String fullPath = httpMethodString + " " + resourcePathString + methodPathString;
|
|
||||||
|
|
||||||
Pattern pattern = Pattern.compile("^ *(" + httpMethodString + " *)?" + getHelpPatternForPath(resourcePathString + methodPathString));
|
|
||||||
result.add(new HelpInfo(pattern, fullPath, description, success, errors));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort by path
|
|
||||||
result.sort((h1, h2) -> h1.fullPath.compareTo(h2.fullPath));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getApiErrorCode(Extension[] extensions) {
|
|
||||||
if(extensions == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
for(Extension extension : extensions) {
|
|
||||||
if(extension.name() != null && !extension.name().isEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for(ExtensionProperty prop : extension.properties()) {
|
|
||||||
if(Constants.API_ERROR_CODE_EXTENSION_NAME.equals(prop.name())) {
|
|
||||||
return prop.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getContextPath(Extension[] extensions) {
|
|
||||||
return getTranslationExtensionValue(extensions, Constants.TRANSLATION_PATH_EXTENSION_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getDescriptionTranslationKey(Extension[] extensions) {
|
|
||||||
return getTranslationExtensionValue(extensions, Constants.TRANSLATION_ANNOTATION_DESCRIPTION_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTranslationExtensionValue(Extension[] extensions, String key) {
|
|
||||||
if(extensions == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
for(Extension extension : extensions) {
|
|
||||||
if(!Constants.TRANSLATION_EXTENSION_NAME.equals(extension.name()))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for(ExtensionProperty prop : extension.properties()) {
|
|
||||||
if(key.equals(prop.name())) {
|
|
||||||
return prop.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getHelpPatternForPath(String path) {
|
|
||||||
path = path
|
|
||||||
.replaceAll("\\.", "\\.") // escapes "." as "\."
|
|
||||||
.replaceAll("\\{.*?\\}", ".*?"); // replace placeholders "{...}" by the "ungreedy match anything" pattern ".*?"
|
|
||||||
|
|
||||||
// arrange the regex pattern so that it also matches partial
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
String[] parts = path.split("/");
|
|
||||||
for (int i = 0; i < parts.length; i++) {
|
|
||||||
if (i != 0) {
|
|
||||||
result.append("(/"); // opening bracket
|
|
||||||
}
|
|
||||||
result.append(parts[i]);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < parts.length - 1; i++) {
|
|
||||||
result.append(")?"); // closing bracket
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String executeCommand(String command) {
|
|
||||||
// check if this is a help command
|
|
||||||
Matcher match = HELP_COMMAND_PATTERN.matcher(command);
|
|
||||||
if (match.matches()) {
|
|
||||||
command = match.group("command");
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
|
|
||||||
boolean showAll = command.trim().equalsIgnoreCase("all");
|
|
||||||
for (HelpInfo helpString : helpInfos) {
|
|
||||||
if (showAll || helpString.pattern.matcher(command).matches()) {
|
|
||||||
appendHelp(result, helpString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
match = COMMAND_PATTERN.matcher(command);
|
|
||||||
if(!match.matches())
|
|
||||||
return this.translator.translate(TRANSLATION_CONTEXT_PATH, "invalid command", "Invalid command! \nType 'help all' to get a list of commands.");
|
|
||||||
|
|
||||||
// send the command to the API service
|
|
||||||
String method = match.group("method");
|
|
||||||
String path = match.group("path");
|
|
||||||
String url = String.format("http://127.0.0.1:%d/%s", Settings.getInstance().getRpcPort(), path);
|
|
||||||
|
|
||||||
Client client = ClientBuilder.newClient();
|
|
||||||
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); // workaround for non-standard HTTP methods like PATCH
|
|
||||||
WebTarget wt = client.target(url);
|
|
||||||
Invocation.Builder builder = wt.request(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN);
|
|
||||||
Response response = builder.method(method);
|
|
||||||
|
|
||||||
// send back result
|
|
||||||
final String body = response.readEntity(String.class);
|
|
||||||
final int status = response.getStatus();
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
if(status >= 400) {
|
|
||||||
if(StringUtils.isBlank(body)) {
|
|
||||||
result.append(
|
|
||||||
this.translator.translate(TRANSLATION_CONTEXT_PATH, "error without body", "HTTP Status ${STATUS}",
|
|
||||||
new AbstractMap.SimpleEntry<>("STATUS", status)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}else{
|
|
||||||
result.append(
|
|
||||||
this.translator.translate(TRANSLATION_CONTEXT_PATH, "error with body", "HTTP Status ${STATUS}: ${BODY}",
|
|
||||||
new AbstractMap.SimpleEntry<>("STATUS", status),
|
|
||||||
new AbstractMap.SimpleEntry<>("BODY", body)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
result.append("\n");
|
|
||||||
result.append(this.translator.translate(TRANSLATION_CONTEXT_PATH, "error footer", "Type 'help all' to get a list of commands."));
|
|
||||||
} else {
|
|
||||||
result.append(body);
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendHelp(StringBuilder builder, HelpInfo help) {
|
|
||||||
builder.append(help.fullPath + "\n");
|
|
||||||
builder.append(" " + help.description + "\n");
|
|
||||||
if(help.success != null && help.success.size() > 0) {
|
|
||||||
builder.append(" ");
|
|
||||||
builder.append(this.translator.translate(TRANSLATION_CONTEXT_PATH, "help: success responses", "On success returns:"));
|
|
||||||
builder.append("\n");
|
|
||||||
for(String content : help.success) {
|
|
||||||
builder.append(" " + content + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(help.errors != null && help.errors.size() > 0) {
|
|
||||||
builder.append(" ");
|
|
||||||
builder.append(this.translator.translate(TRANSLATION_CONTEXT_PATH, "help: failure responses", "On failure returns:"));
|
|
||||||
builder.append("\n");
|
|
||||||
for(String content : help.errors) {
|
|
||||||
builder.append(" " + content + "\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,200 +0,0 @@
|
|||||||
package api;
|
|
||||||
|
|
||||||
import globalization.Translator;
|
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ApiErrorFactory {
|
|
||||||
|
|
||||||
private class ErrorMessageEntry {
|
|
||||||
|
|
||||||
String templateKey;
|
|
||||||
String defaultTemplate;
|
|
||||||
AbstractMap.Entry<String, Object>[] templateValues;
|
|
||||||
|
|
||||||
public ErrorMessageEntry(String templateKey, String defaultTemplate, AbstractMap.Entry<String, Object>... templateValues) {
|
|
||||||
this.templateKey = templateKey;
|
|
||||||
this.defaultTemplate = defaultTemplate;
|
|
||||||
this.templateValues = templateValues;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Translator translator;
|
|
||||||
private Map<ApiError, ErrorMessageEntry> errorMessages;
|
|
||||||
|
|
||||||
public ApiErrorFactory(Translator translator) {
|
|
||||||
this.translator = translator;
|
|
||||||
|
|
||||||
this.errorMessages = new HashMap<ApiError, ErrorMessageEntry>();
|
|
||||||
|
|
||||||
//COMMON
|
|
||||||
this.errorMessages.put(ApiError.UNKNOWN, createErrorMessageEntry(ApiError.UNKNOWN, "unknown error"));
|
|
||||||
this.errorMessages.put(ApiError.JSON, createErrorMessageEntry(ApiError.JSON, "failed to parse json message"));
|
|
||||||
this.errorMessages.put(ApiError.NO_BALANCE, createErrorMessageEntry(ApiError.NO_BALANCE, "not enough balance"));
|
|
||||||
this.errorMessages.put(ApiError.NOT_YET_RELEASED, createErrorMessageEntry(ApiError.NOT_YET_RELEASED, "that feature is not yet released"));
|
|
||||||
this.errorMessages.put(ApiError.UNAUTHORIZED, createErrorMessageEntry(ApiError.UNAUTHORIZED, "api call unauthorized"));
|
|
||||||
this.errorMessages.put(ApiError.REPOSITORY_ISSUE, createErrorMessageEntry(ApiError.REPOSITORY_ISSUE, "repository error"));
|
|
||||||
|
|
||||||
//VALIDATION
|
|
||||||
this.errorMessages.put(ApiError.INVALID_SIGNATURE, createErrorMessageEntry(ApiError.INVALID_SIGNATURE, "invalid signature"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_ADDRESS, createErrorMessageEntry(ApiError.INVALID_ADDRESS, "invalid address"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_SEED, createErrorMessageEntry(ApiError.INVALID_SEED, "invalid seed"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_AMOUNT, createErrorMessageEntry(ApiError.INVALID_AMOUNT, "invalid amount"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_FEE, createErrorMessageEntry(ApiError.INVALID_FEE, "invalid fee"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_SENDER, createErrorMessageEntry(ApiError.INVALID_SENDER, "invalid sender"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_RECIPIENT, createErrorMessageEntry(ApiError.INVALID_RECIPIENT, "invalid recipient"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_NAME_LENGTH, createErrorMessageEntry(ApiError.INVALID_NAME_LENGTH, "invalid name length"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_VALUE_LENGTH, createErrorMessageEntry(ApiError.INVALID_VALUE_LENGTH, "invalid value length"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_NAME_OWNER, createErrorMessageEntry(ApiError.INVALID_NAME_OWNER, "invalid name owner"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_BUYER, createErrorMessageEntry(ApiError.INVALID_BUYER, "invalid buyer"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_PUBLIC_KEY, createErrorMessageEntry(ApiError.INVALID_PUBLIC_KEY, "invalid public key"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_OPTIONS_LENGTH, createErrorMessageEntry(ApiError.INVALID_OPTIONS_LENGTH, "invalid options length"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_OPTION_LENGTH, createErrorMessageEntry(ApiError.INVALID_OPTION_LENGTH, "invalid option length"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_DATA, createErrorMessageEntry(ApiError.INVALID_DATA, "invalid data"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_DATA_LENGTH, createErrorMessageEntry(ApiError.INVALID_DATA_LENGTH, "invalid data length"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_UPDATE_VALUE, createErrorMessageEntry(ApiError.INVALID_UPDATE_VALUE, "invalid update value"));
|
|
||||||
this.errorMessages.put(ApiError.KEY_ALREADY_EXISTS, createErrorMessageEntry(ApiError.KEY_ALREADY_EXISTS, "key already exists, edit is false"));
|
|
||||||
this.errorMessages.put(ApiError.KEY_NOT_EXISTS, createErrorMessageEntry(ApiError.KEY_NOT_EXISTS, "the key does not exist"));
|
|
||||||
// TODO
|
|
||||||
// this.errorMessages.put(ApiError.LAST_KEY_IS_DEFAULT_KEY_ERROR, createErrorMessageEntry(ApiError.LAST_KEY_IS_DEFAULT_KEY_ERROR,
|
|
||||||
// "you can't delete the key \"${key}\" if it is the only key",
|
|
||||||
// new AbstractMap.SimpleEntry<String.Object>("key", Qorakeys.DEFAULT.toString())));
|
|
||||||
this.errorMessages.put(ApiError.FEE_LESS_REQUIRED, createErrorMessageEntry(ApiError.FEE_LESS_REQUIRED, "fee less required"));
|
|
||||||
this.errorMessages.put(ApiError.WALLET_NOT_IN_SYNC, createErrorMessageEntry(ApiError.WALLET_NOT_IN_SYNC, "wallet needs to be synchronized"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_NETWORK_ADDRESS, createErrorMessageEntry(ApiError.INVALID_NETWORK_ADDRESS, "invalid network address"));
|
|
||||||
this.errorMessages.put(ApiError.ADDRESS_NO_EXISTS, createErrorMessageEntry(ApiError.ADDRESS_NO_EXISTS, "account address does not exist"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_CRITERIA, createErrorMessageEntry(ApiError.INVALID_CRITERIA, "invalid search criteria"));
|
|
||||||
|
|
||||||
//WALLET
|
|
||||||
this.errorMessages.put(ApiError.WALLET_NO_EXISTS, createErrorMessageEntry(ApiError.WALLET_NO_EXISTS, "wallet does not exist"));
|
|
||||||
this.errorMessages.put(ApiError.WALLET_ADDRESS_NO_EXISTS, createErrorMessageEntry(ApiError.WALLET_ADDRESS_NO_EXISTS, "address does not exist in wallet"));
|
|
||||||
this.errorMessages.put(ApiError.WALLET_LOCKED, createErrorMessageEntry(ApiError.WALLET_LOCKED, "wallet is locked"));
|
|
||||||
this.errorMessages.put(ApiError.WALLET_ALREADY_EXISTS, createErrorMessageEntry(ApiError.WALLET_ALREADY_EXISTS, "wallet already exists"));
|
|
||||||
this.errorMessages.put(ApiError.WALLET_API_CALL_FORBIDDEN_BY_USER, createErrorMessageEntry(ApiError.WALLET_API_CALL_FORBIDDEN_BY_USER, "wallet denied api call"));
|
|
||||||
|
|
||||||
//BLOCK
|
|
||||||
this.errorMessages.put(ApiError.BLOCK_NO_EXISTS, createErrorMessageEntry(ApiError.BLOCK_NO_EXISTS, "block does not exist"));
|
|
||||||
|
|
||||||
//TRANSACTIONS
|
|
||||||
this.errorMessages.put(ApiError.TRANSACTION_NO_EXISTS, createErrorMessageEntry(ApiError.TRANSACTION_NO_EXISTS, "transaction does not exist"));
|
|
||||||
this.errorMessages.put(ApiError.PUBLIC_KEY_NOT_FOUND, createErrorMessageEntry(ApiError.PUBLIC_KEY_NOT_FOUND, "public key not found"));
|
|
||||||
|
|
||||||
//NAMING
|
|
||||||
this.errorMessages.put(ApiError.NAME_NO_EXISTS, createErrorMessageEntry(ApiError.NAME_NO_EXISTS, "name does not exist"));
|
|
||||||
this.errorMessages.put(ApiError.NAME_ALREADY_EXISTS, createErrorMessageEntry(ApiError.NAME_ALREADY_EXISTS, "name already exists"));
|
|
||||||
this.errorMessages.put(ApiError.NAME_ALREADY_FOR_SALE, createErrorMessageEntry(ApiError.NAME_ALREADY_FOR_SALE, "name already for sale"));
|
|
||||||
this.errorMessages.put(ApiError.NAME_NOT_LOWER_CASE, createErrorMessageEntry(ApiError.NAME_NOT_LOWER_CASE, "name must be lower case"));
|
|
||||||
this.errorMessages.put(ApiError.NAME_SALE_NO_EXISTS, createErrorMessageEntry(ApiError.NAME_SALE_NO_EXISTS, "namesale does not exist"));
|
|
||||||
this.errorMessages.put(ApiError.BUYER_ALREADY_OWNER, createErrorMessageEntry(ApiError.BUYER_ALREADY_OWNER, "buyer is already owner"));
|
|
||||||
|
|
||||||
//POLLS
|
|
||||||
this.errorMessages.put(ApiError.POLL_NO_EXISTS, createErrorMessageEntry(ApiError.POLL_NO_EXISTS, "poll does not exist"));
|
|
||||||
this.errorMessages.put(ApiError.POLL_ALREADY_EXISTS, createErrorMessageEntry(ApiError.POLL_ALREADY_EXISTS, "poll already exists"));
|
|
||||||
this.errorMessages.put(ApiError.DUPLICATE_OPTION, createErrorMessageEntry(ApiError.DUPLICATE_OPTION, "not all options are unique"));
|
|
||||||
this.errorMessages.put(ApiError.POLL_OPTION_NO_EXISTS, createErrorMessageEntry(ApiError.POLL_OPTION_NO_EXISTS, "option does not exist"));
|
|
||||||
this.errorMessages.put(ApiError.ALREADY_VOTED_FOR_THAT_OPTION, createErrorMessageEntry(ApiError.ALREADY_VOTED_FOR_THAT_OPTION, "already voted for that option"));
|
|
||||||
|
|
||||||
//ASSETS
|
|
||||||
this.errorMessages.put(ApiError.INVALID_ASSET_ID, createErrorMessageEntry(ApiError.INVALID_ASSET_ID, "invalid asset id"));
|
|
||||||
|
|
||||||
//NAME PAYMENTS
|
|
||||||
// TODO
|
|
||||||
// this.errorMessages.put(ApiError.NAME_NOT_REGISTERED, createErrorMessageEntry(ApiError.NAME_NOT_REGISTERED, NameResult.NAME_NOT_REGISTERED.getStatusMessage()));
|
|
||||||
// this.errorMessages.put(ApiError.NAME_FOR_SALE, createErrorMessageEntry(ApiError.NAME_FOR_SALE, NameResult.NAME_FOR_SALE.getStatusMessage()));
|
|
||||||
// this.errorMessages.put(ApiError.NAME_WITH_SPACE, createErrorMessageEntry(ApiError.NAME_WITH_SPACE, NameResult.NAME_WITH_SPACE.getStatusMessage()));
|
|
||||||
//AT
|
|
||||||
// TODO
|
|
||||||
// this.errorMessages.put(ApiError.INVALID_DESC_LENGTH, createErrorMessageEntry(ApiError.INVALID_DESC_LENGTH,
|
|
||||||
// "invalid description length. max length ${MAX_LENGTH}",
|
|
||||||
// new AbstractMap.SimpleEntry<String, Object>("MAX_LENGTH", AT_Constants.DESC_MAX_LENGTH));
|
|
||||||
this.errorMessages.put(ApiError.EMPTY_CODE, createErrorMessageEntry(ApiError.EMPTY_CODE, "code is empty"));
|
|
||||||
this.errorMessages.put(ApiError.DATA_SIZE, createErrorMessageEntry(ApiError.DATA_SIZE, "invalid data length"));
|
|
||||||
this.errorMessages.put(ApiError.NULL_PAGES, createErrorMessageEntry(ApiError.NULL_PAGES, "invalid pages"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_TYPE_LENGTH, createErrorMessageEntry(ApiError.INVALID_TYPE_LENGTH, "invalid type length"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_TAGS_LENGTH, createErrorMessageEntry(ApiError.INVALID_TAGS_LENGTH, "invalid tags length"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_CREATION_BYTES, createErrorMessageEntry(ApiError.INVALID_CREATION_BYTES, "error in creation bytes"));
|
|
||||||
|
|
||||||
//BLOG
|
|
||||||
this.errorMessages.put(ApiError.BODY_EMPTY, createErrorMessageEntry(ApiError.BODY_EMPTY, "invalid body it must not be empty"));
|
|
||||||
this.errorMessages.put(ApiError.BLOG_DISABLED, createErrorMessageEntry(ApiError.BLOG_DISABLED, "this blog is disabled"));
|
|
||||||
this.errorMessages.put(ApiError.NAME_NOT_OWNER, createErrorMessageEntry(ApiError.NAME_NOT_OWNER, "the creator address does not own the author name"));
|
|
||||||
// this.errorMessages.put(ApiError.TX_AMOUNT, createErrorMessageEntry(ApiError.TX_AMOUNT,
|
|
||||||
// "the data size is too large - currently only ${BATCH_TX_AMOUNT} arbitrary transactions are allowed at once!",
|
|
||||||
// new AbstractMap.SimpleEntry<String,Object>("BATCH_TX_AMOUNT", BATCH_TX_AMOUNT)));
|
|
||||||
this.errorMessages.put(ApiError.BLOG_ENTRY_NO_EXISTS, createErrorMessageEntry(ApiError.BLOG_ENTRY_NO_EXISTS, "transaction with this signature contains no entries!"));
|
|
||||||
this.errorMessages.put(ApiError.BLOG_EMPTY, createErrorMessageEntry(ApiError.BLOG_EMPTY, "this blog is empty"));
|
|
||||||
this.errorMessages.put(ApiError.POSTID_EMPTY, createErrorMessageEntry(ApiError.POSTID_EMPTY, "the attribute postid is empty! this is the signature of the post you want to comment"));
|
|
||||||
this.errorMessages.put(ApiError.POST_NOT_EXISTING, createErrorMessageEntry(ApiError.POST_NOT_EXISTING, "for the given postid no blogpost to comment was found"));
|
|
||||||
this.errorMessages.put(ApiError.COMMENTING_DISABLED, createErrorMessageEntry(ApiError.COMMENTING_DISABLED, "commenting is for this blog disabled"));
|
|
||||||
this.errorMessages.put(ApiError.COMMENT_NOT_EXISTING, createErrorMessageEntry(ApiError.COMMENT_NOT_EXISTING, "for the given signature no comment was found"));
|
|
||||||
this.errorMessages.put(ApiError.INVALID_COMMENT_OWNER, createErrorMessageEntry(ApiError.INVALID_COMMENT_OWNER, "invalid comment owner"));
|
|
||||||
|
|
||||||
//MESSAGES
|
|
||||||
this.errorMessages.put(ApiError.MESSAGE_FORMAT_NOT_HEX, createErrorMessageEntry(ApiError.MESSAGE_FORMAT_NOT_HEX, "the Message format is not hex - correct the text or use isTextMessage = true"));
|
|
||||||
this.errorMessages.put(ApiError.MESSAGE_BLANK, createErrorMessageEntry(ApiError.MESSAGE_BLANK, "The message attribute is missing or content is blank"));
|
|
||||||
this.errorMessages.put(ApiError.NO_PUBLIC_KEY, createErrorMessageEntry(ApiError.NO_PUBLIC_KEY, "The recipient has not yet performed any action in the blockchain.\nYou can't send an encrypted message to them."));
|
|
||||||
this.errorMessages.put(ApiError.MESSAGESIZE_EXCEEDED, createErrorMessageEntry(ApiError.MESSAGESIZE_EXCEEDED, "Message size exceeded!"));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//XXX: replace singleton pattern by dependency injection?
|
|
||||||
private static ApiErrorFactory instance;
|
|
||||||
|
|
||||||
public static ApiErrorFactory getInstance() {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new ApiErrorFactory(Translator.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ErrorMessageEntry createErrorMessageEntry(ApiError errorCode, String defaultTemplate, AbstractMap.SimpleEntry<String, Object>... templateValues) {
|
|
||||||
String templateKey = String.format(Constants.APIERROR_KEY, errorCode.name());
|
|
||||||
return new ErrorMessageEntry(templateKey, defaultTemplate, templateValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorMessage(ApiError error) {
|
|
||||||
return getErrorMessage(null, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorMessage(Locale locale, ApiError error) {
|
|
||||||
ErrorMessageEntry errorMessage = this.errorMessages.get(error);
|
|
||||||
String message = this.translator.translate(locale, Constants.APIERROR_CONTEXT_PATH, errorMessage.templateKey, errorMessage.defaultTemplate, errorMessage.templateValues);
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApiException createError(ApiError error) {
|
|
||||||
return createError(error, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApiException createError(Locale locale, ApiError error) {
|
|
||||||
return createError(locale, error, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApiException createError(ApiError error, Throwable throwable) {
|
|
||||||
return createError(null, error, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ApiException createError(Locale locale, ApiError error, Throwable throwable) {
|
|
||||||
// TODO: handle AT errors
|
|
||||||
// old AT error handling
|
|
||||||
// JSONObject jsonObject = new JSONObject();
|
|
||||||
// jsonObject.put("error", error);
|
|
||||||
// if ( error > Transaction.AT_ERROR )
|
|
||||||
// {
|
|
||||||
// jsonObject.put("message", AT_Error.getATError(error - Transaction.AT_ERROR) );
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// jsonObject.put("message", this.errorMessages.get(error));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// return new WebApplicationException(Response.status(Response.Status.BAD_REQUEST).entity(jsonObject.toJSONString()).build());
|
|
||||||
|
|
||||||
String message = getErrorMessage(locale, error);
|
|
||||||
return new ApiException(error.getStatus(), error.getCode(), message, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package api;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.FileSystems;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.ws.rs.GET;
|
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.core.Context;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
|
|
||||||
@Path("/")
|
|
||||||
public class BlockExplorerResource {
|
|
||||||
|
|
||||||
@Context
|
|
||||||
HttpServletRequest request;
|
|
||||||
|
|
||||||
public BlockExplorerResource() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/block-explorer.html")
|
|
||||||
@Operation(hidden = true)
|
|
||||||
public String getBlockExplorer() {
|
|
||||||
try {
|
|
||||||
byte[] htmlBytes = Files.readAllBytes(FileSystems.getDefault().getPath("block-explorer.html"));
|
|
||||||
return new String(htmlBytes, "UTF-8");
|
|
||||||
} catch (IOException e) {
|
|
||||||
return "block-explorer.html not found";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
package globalization;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FilenameFilter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import settings.Settings;
|
|
||||||
|
|
||||||
/** Providing multi-language BIP39 word lists, downloaded from https://github.com/bitcoin/bips/tree/master/bip-0039 */
|
|
||||||
public class BIP39WordList {
|
|
||||||
|
|
||||||
private static BIP39WordList instance;
|
|
||||||
|
|
||||||
private static Map<String, List<String>> wordListsByLang;
|
|
||||||
|
|
||||||
private BIP39WordList() {
|
|
||||||
wordListsByLang = new HashMap<>();
|
|
||||||
|
|
||||||
String path = Settings.getInstance().translationsPath();
|
|
||||||
File dir = new File(path);
|
|
||||||
File[] files = dir.listFiles(new FilenameFilter() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(File dir, String name) {
|
|
||||||
return name.startsWith("BIP39.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (File file : files) {
|
|
||||||
String lang = file.getName().substring(6, 8);
|
|
||||||
List<String> words = Files.readAllLines(file.toPath());
|
|
||||||
wordListsByLang.put(lang, words);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Unable to read BIP39 word list", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized BIP39WordList getInstance() {
|
|
||||||
if (instance == null)
|
|
||||||
instance = new BIP39WordList();
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getByLang(String lang) {
|
|
||||||
return Collections.unmodifiableList(wordListsByLang.get(lang));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package globalization;
|
|
||||||
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
public class ContextPaths {
|
|
||||||
|
|
||||||
public static boolean isValidKey(String value) {
|
|
||||||
return !value.contains("/") && !ContextPaths.containsParentReference(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean containsParentReference(String value) {
|
|
||||||
for(String part : value.split("/")) {
|
|
||||||
if(part.equalsIgnoreCase(".."))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String combinePaths(String left, String right) {
|
|
||||||
left = (left != null) ? left : "";
|
|
||||||
right = (right != null) ? right : "";
|
|
||||||
return Paths.get("/", left, right).normalize().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getParent(String path) {
|
|
||||||
return combinePaths(path, "..");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isRoot(String path) {
|
|
||||||
return path.equals("/");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
package globalization;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
public class TranslationEntry {
|
|
||||||
private Locale locale;
|
|
||||||
private String path;
|
|
||||||
private String template;
|
|
||||||
|
|
||||||
public TranslationEntry(Locale locale, String path, String template) {
|
|
||||||
this.locale = locale;
|
|
||||||
this.path = path;
|
|
||||||
this.template = template;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Locale locale() {
|
|
||||||
return this.locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String path() {
|
|
||||||
return this.path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String template() {
|
|
||||||
return this.template;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("{locale: '%s', path: '%s', template: '%s'}", this.locale, this.path, this.template);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,252 +0,0 @@
|
|||||||
/*
|
|
||||||
* To change this license header, choose License Headers in Project Properties.
|
|
||||||
* To change this template file, choose Tools | Templates
|
|
||||||
* and open the template in the editor.
|
|
||||||
*/
|
|
||||||
package globalization;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import javax.xml.namespace.QName;
|
|
||||||
|
|
||||||
import javax.xml.stream.XMLEventReader;
|
|
||||||
import javax.xml.stream.XMLInputFactory;
|
|
||||||
import javax.xml.stream.XMLStreamException;
|
|
||||||
import javax.xml.stream.events.*;
|
|
||||||
import org.apache.commons.text.StringEscapeUtils;
|
|
||||||
|
|
||||||
public class TranslationXmlStreamReader {
|
|
||||||
|
|
||||||
private class State {
|
|
||||||
|
|
||||||
public final Locale locale;
|
|
||||||
public final String path;
|
|
||||||
|
|
||||||
public State(Locale locale, String path) {
|
|
||||||
this.locale = locale;
|
|
||||||
this.path = path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String LOCALIZATION_TAG_NAME = "localization";
|
|
||||||
|
|
||||||
private static final String CONTEXT_TAG_NAME = "context";
|
|
||||||
private static final String CONTEXT_LOCALE_ATTRIBUTE_NAME = "locale";
|
|
||||||
private static final String CONTEXT_PATH_ATTRIBUTE_NAME = "path";
|
|
||||||
|
|
||||||
private static final String TRANSLATION_TAG_NAME = "translation";
|
|
||||||
private static final String TRANSLATION_KEY_ATTRIBUTE_NAME = "key";
|
|
||||||
private static final String TRANSLATION_TEMPLATE_ATTRIBUTE_NAME = "template";
|
|
||||||
|
|
||||||
public Iterable<TranslationEntry> ReadFrom(InputStream stream) throws XMLStreamException {
|
|
||||||
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
|
|
||||||
XMLEventReader eventReader = inputFactory.createXMLEventReader(stream);
|
|
||||||
|
|
||||||
XMLEvent element = eventReader.nextEvent();
|
|
||||||
if(!element.isStartDocument())
|
|
||||||
throw new javax.xml.stream.XMLStreamException("XML declaration <?xml ... ?> must be first in the document");
|
|
||||||
|
|
||||||
State state = new State(Locale.forLanguageTag("default"), "/");
|
|
||||||
|
|
||||||
List<TranslationEntry> result = new ArrayList<>();
|
|
||||||
if (eventReader.hasNext())
|
|
||||||
{
|
|
||||||
XMLEvent event = eventReader.nextTag();
|
|
||||||
if (isStartElement(event, LOCALIZATION_TAG_NAME))
|
|
||||||
{
|
|
||||||
processLocalization(eventReader, (StartElement)event, state, result);
|
|
||||||
} else {
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected element: " + event.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (eventReader.hasNext())
|
|
||||||
{
|
|
||||||
XMLEvent event = eventReader.nextEvent();
|
|
||||||
switch(event.getEventType()) {
|
|
||||||
case XMLEvent.COMMENT:
|
|
||||||
break;
|
|
||||||
case XMLEvent.CHARACTERS:
|
|
||||||
if(!event.asCharacters().isIgnorableWhiteSpace())
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected content after end of root element: " + event.toString());
|
|
||||||
break;
|
|
||||||
case XMLEvent.END_DOCUMENT:
|
|
||||||
return result;
|
|
||||||
default:
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected content after end of root element: " + event.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new javax.xml.stream.XMLStreamException("End of document not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processLocalization(XMLEventReader eventReader, StartElement element, State state, List<TranslationEntry> result) throws XMLStreamException {
|
|
||||||
assureStartElement(element, LOCALIZATION_TAG_NAME);
|
|
||||||
|
|
||||||
Iterator<Attribute> attributes = element.getAttributes();
|
|
||||||
while (attributes.hasNext())
|
|
||||||
{
|
|
||||||
Attribute attribute = attributes.next();
|
|
||||||
QName name = attribute.getName();
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected attribute: " + name);
|
|
||||||
}
|
|
||||||
|
|
||||||
XMLEvent event;
|
|
||||||
while(!(event = eventReader.nextTag()).isEndElement()) {
|
|
||||||
if(event.isStartElement()) {
|
|
||||||
StartElement childElement = (StartElement)event;
|
|
||||||
switch(childElement.getName().toString()) {
|
|
||||||
case CONTEXT_TAG_NAME:
|
|
||||||
processContext(eventReader, childElement, state, result);
|
|
||||||
break;
|
|
||||||
case TRANSLATION_TAG_NAME:
|
|
||||||
processTranslation(eventReader, childElement, state, result);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected element: " + event.toString());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected content: " + event.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assureEndElement(event, LOCALIZATION_TAG_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processContext(XMLEventReader eventReader, StartElement element, State state, List<TranslationEntry> result) throws XMLStreamException {
|
|
||||||
assureStartElement(element, CONTEXT_TAG_NAME);
|
|
||||||
|
|
||||||
Locale locale = state.locale;
|
|
||||||
String contextPath = state.path;
|
|
||||||
|
|
||||||
Iterator<Attribute> attributes = element.getAttributes();
|
|
||||||
while (attributes.hasNext())
|
|
||||||
{
|
|
||||||
Attribute attribute = attributes.next();
|
|
||||||
QName name = attribute.getName();
|
|
||||||
String value = attribute.getValue();
|
|
||||||
switch(name.toString()) {
|
|
||||||
case CONTEXT_LOCALE_ATTRIBUTE_NAME:
|
|
||||||
locale = Locale.forLanguageTag(value);
|
|
||||||
break;
|
|
||||||
case CONTEXT_PATH_ATTRIBUTE_NAME:
|
|
||||||
assureIsValidPathExtension(value);
|
|
||||||
contextPath = ContextPaths.combinePaths(contextPath, value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected attribute: " + name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state = new State(locale, contextPath);
|
|
||||||
|
|
||||||
XMLEvent event;
|
|
||||||
while(!(event = eventReader.nextTag()).isEndElement()) {
|
|
||||||
if(event.isStartElement()) {
|
|
||||||
StartElement childElement = (StartElement)event;
|
|
||||||
switch(childElement.getName().toString()) {
|
|
||||||
case CONTEXT_TAG_NAME:
|
|
||||||
processContext(eventReader, childElement, state, result);
|
|
||||||
break;
|
|
||||||
case TRANSLATION_TAG_NAME:
|
|
||||||
processTranslation(eventReader, childElement, state, result);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected element: " + event.toString());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected content: " + event.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assureEndElement(event, CONTEXT_TAG_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void processTranslation(XMLEventReader eventReader, StartElement element, State state, List<TranslationEntry> result) throws XMLStreamException {
|
|
||||||
assureStartElement(element, TRANSLATION_TAG_NAME);
|
|
||||||
|
|
||||||
String path = null;
|
|
||||||
String template = null;
|
|
||||||
|
|
||||||
Iterator<Attribute> attributes = element.getAttributes();
|
|
||||||
while (attributes.hasNext())
|
|
||||||
{
|
|
||||||
Attribute attribute = attributes.next();
|
|
||||||
QName name = attribute.getName();
|
|
||||||
String value = attribute.getValue();
|
|
||||||
switch(name.toString()) {
|
|
||||||
case TRANSLATION_KEY_ATTRIBUTE_NAME:
|
|
||||||
assureIsValidKey(value);
|
|
||||||
path = ContextPaths.combinePaths(state.path, value);
|
|
||||||
break;
|
|
||||||
case TRANSLATION_TEMPLATE_ATTRIBUTE_NAME:
|
|
||||||
template = unescape(value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected attribute: " + name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
XMLEvent event;
|
|
||||||
while(!(event = eventReader.nextTag()).isEndElement()) {
|
|
||||||
if(event.isStartElement()) {
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected element: " + event.toString());
|
|
||||||
} else if(event.isCharacters()) {
|
|
||||||
if(template != null)
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Content must be empty if 'template' attribute is used");
|
|
||||||
template = event.asCharacters().getData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assureEndElement(event, TRANSLATION_TAG_NAME);
|
|
||||||
|
|
||||||
if(path == null)
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Missing attribute: " + TRANSLATION_KEY_ATTRIBUTE_NAME);
|
|
||||||
|
|
||||||
if(template == null)
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Missing attribute: " + TRANSLATION_TEMPLATE_ATTRIBUTE_NAME);
|
|
||||||
|
|
||||||
result.add(new TranslationEntry(state.locale, path, template));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String unescape(String value) {
|
|
||||||
return StringEscapeUtils.unescapeJava(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assureIsValidPathExtension(String value) throws XMLStreamException {
|
|
||||||
if(ContextPaths.containsParentReference(value))
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Parent reference .. is not allowed");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assureIsValidKey(String value) throws XMLStreamException {
|
|
||||||
if(!ContextPaths.isValidKey(value))
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Key is not valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assureStartElement(XMLEvent event, String name) throws XMLStreamException {
|
|
||||||
if(!isStartElement(event, name))
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected start element: " + event.toString() + ", <" + name + "> expected");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assureEndElement(XMLEvent event, String name) throws XMLStreamException {
|
|
||||||
if(!isEndElement(event, name))
|
|
||||||
throw new javax.xml.stream.XMLStreamException("Unexpected end element: " + event.toString() + ", </" + name + "> expected");
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isStartElement(XMLEvent event, String name) {
|
|
||||||
if(!event.isStartElement())
|
|
||||||
return false;
|
|
||||||
StartElement element = ((StartElement)event);
|
|
||||||
return element.getName().toString().equals(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isEndElement(XMLEvent event, String name) {
|
|
||||||
if(!event.isEndElement())
|
|
||||||
return false;
|
|
||||||
EndElement element = ((EndElement)event);
|
|
||||||
return element.getName().toString().equals(name);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<!--
|
|
||||||
To change this license header, choose License Headers in Project Properties.
|
|
||||||
To change this template file, choose Tools | Templates
|
|
||||||
and open the template in the editor.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<xs:schema version="1.0"
|
|
||||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
||||||
elementFormDefault="qualified">
|
|
||||||
|
|
||||||
<xs:complexType name="localizationType">
|
|
||||||
<xs:all>
|
|
||||||
<xs:element name="context" minOccurs="1" maxOccurs="unbounded" />
|
|
||||||
</xs:all>
|
|
||||||
</xs:complexType>
|
|
||||||
|
|
||||||
<xs:complexType name="contextType">
|
|
||||||
<xs:attribute name="path" type="xs:string" minOccurs="0" maxOccurs="1" />
|
|
||||||
<xs:attribute name="locale" type="xs:string" minOccurs="0" maxOccurs="1" />
|
|
||||||
<xs:all>
|
|
||||||
<xs:element type="translation" minOccurs="0" maxOccurs="unbounded" />
|
|
||||||
<xs:element type="context" minOccurs="0" maxOccurs="unbounded" />
|
|
||||||
</xs:all>
|
|
||||||
</xs:complexType>
|
|
||||||
|
|
||||||
<xs:complexType name="translationType">
|
|
||||||
<xs:attribute name="keyPath" type="xs:string" minOccurs="1" maxOccurs="1" />
|
|
||||||
<xs:attribute name="template" type="xs:string" minOccurs="1" maxOccurs="1" />
|
|
||||||
</xs:complexType>
|
|
||||||
|
|
||||||
<xs:element name="localization" type="localizationType" />
|
|
||||||
</xs:schema>
|
|
@ -1,176 +0,0 @@
|
|||||||
package globalization;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FilenameFilter;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import javax.xml.stream.XMLStreamException;
|
|
||||||
import org.apache.commons.text.StringSubstitutor;
|
|
||||||
|
|
||||||
import settings.Settings;
|
|
||||||
|
|
||||||
public class Translator {
|
|
||||||
|
|
||||||
Map<Locale, Map<String, String>> translations = new HashMap<Locale, Map<String, String>>();
|
|
||||||
|
|
||||||
//XXX: replace singleton pattern by dependency injection?
|
|
||||||
private static Translator instance;
|
|
||||||
|
|
||||||
private Translator() {
|
|
||||||
InitializeTranslations();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Translator getInstance() {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new Translator();
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Settings settings() {
|
|
||||||
return Settings.getInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeTranslations() {
|
|
||||||
String path = this.settings().translationsPath();
|
|
||||||
File dir = new File(path);
|
|
||||||
File [] files = dir.listFiles(new FilenameFilter() {
|
|
||||||
@Override
|
|
||||||
public boolean accept(File dir, String name) {
|
|
||||||
return name.endsWith(".xml");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Map<Locale, Map<String, String>> translations = new HashMap<>();
|
|
||||||
TranslationXmlStreamReader translationReader = new TranslationXmlStreamReader();
|
|
||||||
for (File file : files) {
|
|
||||||
Iterable<TranslationEntry> entries = null;
|
|
||||||
try {
|
|
||||||
InputStream stream = new FileInputStream(file);
|
|
||||||
entries = translationReader.ReadFrom(stream);
|
|
||||||
} catch (FileNotFoundException ex) {
|
|
||||||
Logger.getLogger(Translator.class.getName()).log(Level.SEVERE, String.format("Translation file not found: %s", file), ex);
|
|
||||||
} catch (XMLStreamException ex) {
|
|
||||||
Logger.getLogger(Translator.class.getName()).log(Level.SEVERE, String.format("Error in translation file: %s", file), ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(TranslationEntry entry : entries) {
|
|
||||||
Map<String, String> localTranslations = translations.get(entry.locale());
|
|
||||||
if(localTranslations == null) {
|
|
||||||
localTranslations = new HashMap<>();
|
|
||||||
translations.put(entry.locale(), localTranslations);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(localTranslations.containsKey(entry.path())) {
|
|
||||||
Logger.getLogger(Translator.class.getName()).log(Level.SEVERE, String.format("Duplicate entry for locale '%s' and path '%s' in translation file '%s'. Falling back to default translations.", entry.locale(), entry.path(), file));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
localTranslations.put(entry.path(), entry.template());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// everything is fine, so we store all read translations
|
|
||||||
this.translations = translations;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> createMap(Map.Entry<String, Object>[] entries) {
|
|
||||||
HashMap<String, Object> map = new HashMap<>();
|
|
||||||
for (AbstractMap.Entry<String, Object> entry : entries) {
|
|
||||||
map.put(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String translate(Locale locale, String contextPath, String keyPath, AbstractMap.Entry<String, Object>... templateValues) {
|
|
||||||
Map<String, Object> map = createMap(templateValues);
|
|
||||||
return translate(locale, contextPath, keyPath, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String translate(String contextPath, String keyPath, AbstractMap.Entry<String, Object>... templateValues) {
|
|
||||||
Map<String, Object> map = createMap(templateValues);
|
|
||||||
return translate(contextPath, keyPath, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String translate(Locale locale, String contextPath, String keyPath, Map<String, Object> templateValues) {
|
|
||||||
return translate(locale, contextPath, keyPath, null, templateValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String translate(String contextPath, String keyPath, Map<String, Object> templateValues) {
|
|
||||||
return translate(contextPath, keyPath, null, templateValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String translate(Locale locale, String contextPath, String keyPath, String defaultTemplate, AbstractMap.Entry<String, Object>... templateValues) {
|
|
||||||
Map<String, Object> map = createMap(templateValues);
|
|
||||||
return translate(locale, contextPath, keyPath, defaultTemplate, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String translate(String contextPath, String keyPath, String defaultTemplate, AbstractMap.Entry<String, Object>... templateValues) {
|
|
||||||
Map<String, Object> map = createMap(templateValues);
|
|
||||||
return translate(contextPath, keyPath, defaultTemplate, map);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String translate(Locale locale, String contextPath, String keyPath, String defaultTemplate, Map<String, Object> templateValues) {
|
|
||||||
// look for requested language
|
|
||||||
String template = null;
|
|
||||||
if(locale != null)
|
|
||||||
template = getTemplateFromNearestPath(locale, contextPath, keyPath);
|
|
||||||
|
|
||||||
if(template != null)
|
|
||||||
return substitute(template, templateValues);
|
|
||||||
|
|
||||||
return translate(contextPath, keyPath, defaultTemplate, templateValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String translate(String contextPath, String keyPath, String defaultTemplate, Map<String, Object> templateValues) {
|
|
||||||
// scan default languages
|
|
||||||
String template = null;
|
|
||||||
for(String language : this.settings().translationsDefaultLocales()) {
|
|
||||||
Locale defaultLocale = Locale.forLanguageTag(language);
|
|
||||||
template = getTemplateFromNearestPath(defaultLocale, contextPath, keyPath);
|
|
||||||
if(template != null)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(template == null)
|
|
||||||
template = defaultTemplate; // fallback template
|
|
||||||
|
|
||||||
return substitute(template, templateValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String substitute(String template, Map<String, Object> templateValues) {
|
|
||||||
if(templateValues == null)
|
|
||||||
return template;
|
|
||||||
|
|
||||||
StringSubstitutor sub = new StringSubstitutor(templateValues);
|
|
||||||
String result = sub.replace(template);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getTemplateFromNearestPath(Locale locale, String contextPath, String keyPath) {
|
|
||||||
Map<String, String> localTranslations = this.translations.get(locale);
|
|
||||||
if(localTranslations == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
String template = null;
|
|
||||||
while(true) {
|
|
||||||
String path = ContextPaths.combinePaths(contextPath, keyPath);
|
|
||||||
template = localTranslations.get(path);
|
|
||||||
if(template != null)
|
|
||||||
break; // found template
|
|
||||||
if(ContextPaths.isRoot(contextPath))
|
|
||||||
break; // nothing found
|
|
||||||
contextPath = ContextPaths.getParent(contextPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,12 @@
|
|||||||
package api;
|
package api;
|
||||||
|
|
||||||
|
import static java.util.Arrays.stream;
|
||||||
|
import static java.util.stream.Collectors.toMap;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public enum ApiError {
|
public enum ApiError {
|
||||||
//COMMON
|
// COMMON
|
||||||
UNKNOWN(0, 500),
|
UNKNOWN(0, 500),
|
||||||
JSON(1, 400),
|
JSON(1, 400),
|
||||||
NO_BALANCE(2, 422),
|
NO_BALANCE(2, 422),
|
||||||
@ -9,7 +14,7 @@ public enum ApiError {
|
|||||||
UNAUTHORIZED(4, 403),
|
UNAUTHORIZED(4, 403),
|
||||||
REPOSITORY_ISSUE(5, 500),
|
REPOSITORY_ISSUE(5, 500),
|
||||||
|
|
||||||
//VALIDATION
|
// VALIDATION
|
||||||
INVALID_SIGNATURE(101, 400),
|
INVALID_SIGNATURE(101, 400),
|
||||||
INVALID_ADDRESS(102, 400),
|
INVALID_ADDRESS(102, 400),
|
||||||
INVALID_SEED(103, 400),
|
INVALID_SEED(103, 400),
|
||||||
@ -36,22 +41,24 @@ public enum ApiError {
|
|||||||
ADDRESS_NO_EXISTS(124, 404),
|
ADDRESS_NO_EXISTS(124, 404),
|
||||||
INVALID_CRITERIA(125, 400),
|
INVALID_CRITERIA(125, 400),
|
||||||
INVALID_REFERENCE(126, 400),
|
INVALID_REFERENCE(126, 400),
|
||||||
|
TRANSFORMATION_ERROR(127, 400),
|
||||||
|
|
||||||
//WALLET
|
// WALLET
|
||||||
WALLET_NO_EXISTS(201, 404),
|
WALLET_NO_EXISTS(201, 404),
|
||||||
WALLET_ADDRESS_NO_EXISTS(202, 404),
|
WALLET_ADDRESS_NO_EXISTS(202, 404),
|
||||||
WALLET_LOCKED(203, 422),
|
WALLET_LOCKED(203, 422),
|
||||||
WALLET_ALREADY_EXISTS(204, 422),
|
WALLET_ALREADY_EXISTS(204, 422),
|
||||||
WALLET_API_CALL_FORBIDDEN_BY_USER(205, 403),
|
WALLET_API_CALL_FORBIDDEN_BY_USER(205, 403),
|
||||||
|
|
||||||
//BLOCKS
|
// BLOCKS
|
||||||
BLOCK_NO_EXISTS(301, 404),
|
BLOCK_NO_EXISTS(301, 404),
|
||||||
|
|
||||||
//TRANSACTIONS
|
// TRANSACTIONS
|
||||||
TRANSACTION_NO_EXISTS(311, 404),
|
TRANSACTION_NO_EXISTS(311, 404),
|
||||||
PUBLIC_KEY_NOT_FOUND(304, 404),
|
PUBLIC_KEY_NOT_FOUND(304, 404),
|
||||||
|
TRANSACTION_INVALID(312, 400),
|
||||||
|
|
||||||
//NAMING
|
// NAMING
|
||||||
NAME_NO_EXISTS(401, 404),
|
NAME_NO_EXISTS(401, 404),
|
||||||
NAME_ALREADY_EXISTS(402, 422),
|
NAME_ALREADY_EXISTS(402, 422),
|
||||||
NAME_ALREADY_FOR_SALE(403, 422),
|
NAME_ALREADY_FOR_SALE(403, 422),
|
||||||
@ -59,24 +66,24 @@ public enum ApiError {
|
|||||||
NAME_SALE_NO_EXISTS(410, 404),
|
NAME_SALE_NO_EXISTS(410, 404),
|
||||||
BUYER_ALREADY_OWNER(411, 422),
|
BUYER_ALREADY_OWNER(411, 422),
|
||||||
|
|
||||||
//POLLS
|
// POLLS
|
||||||
POLL_NO_EXISTS(501, 404),
|
POLL_NO_EXISTS(501, 404),
|
||||||
POLL_ALREADY_EXISTS(502, 422),
|
POLL_ALREADY_EXISTS(502, 422),
|
||||||
DUPLICATE_OPTION(503, 422),
|
DUPLICATE_OPTION(503, 422),
|
||||||
POLL_OPTION_NO_EXISTS(504, 404),
|
POLL_OPTION_NO_EXISTS(504, 404),
|
||||||
ALREADY_VOTED_FOR_THAT_OPTION(505, 422),
|
ALREADY_VOTED_FOR_THAT_OPTION(505, 422),
|
||||||
|
|
||||||
//ASSET
|
// ASSET
|
||||||
INVALID_ASSET_ID(601, 400),
|
INVALID_ASSET_ID(601, 400),
|
||||||
INVALID_ORDER_ID(602, 400),
|
INVALID_ORDER_ID(602, 400),
|
||||||
ORDER_NO_EXISTS(603, 404),
|
ORDER_NO_EXISTS(603, 404),
|
||||||
|
|
||||||
//NAME PAYMENTS
|
// NAME PAYMENTS
|
||||||
NAME_NOT_REGISTERED(701, 422),
|
NAME_NOT_REGISTERED(701, 422),
|
||||||
NAME_FOR_SALE(702, 422),
|
NAME_FOR_SALE(702, 422),
|
||||||
NAME_WITH_SPACE(703, 422),
|
NAME_WITH_SPACE(703, 422),
|
||||||
|
|
||||||
//ATs
|
// ATs
|
||||||
INVALID_DESC_LENGTH(801, 400),
|
INVALID_DESC_LENGTH(801, 400),
|
||||||
EMPTY_CODE(802, 400),
|
EMPTY_CODE(802, 400),
|
||||||
DATA_SIZE(803, 400),
|
DATA_SIZE(803, 400),
|
||||||
@ -85,7 +92,7 @@ public enum ApiError {
|
|||||||
INVALID_TAGS_LENGTH(806, 400),
|
INVALID_TAGS_LENGTH(806, 400),
|
||||||
INVALID_CREATION_BYTES(809, 400),
|
INVALID_CREATION_BYTES(809, 400),
|
||||||
|
|
||||||
//BLOG/Namestorage
|
// BLOG/Namestorage
|
||||||
BODY_EMPTY(901, 400),
|
BODY_EMPTY(901, 400),
|
||||||
BLOG_DISABLED(902, 403),
|
BLOG_DISABLED(902, 403),
|
||||||
NAME_NOT_OWNER(903, 422),
|
NAME_NOT_OWNER(903, 422),
|
||||||
@ -98,12 +105,14 @@ public enum ApiError {
|
|||||||
COMMENT_NOT_EXISTING(910, 404),
|
COMMENT_NOT_EXISTING(910, 404),
|
||||||
INVALID_COMMENT_OWNER(911, 422),
|
INVALID_COMMENT_OWNER(911, 422),
|
||||||
|
|
||||||
//Messages
|
// Messages
|
||||||
MESSAGE_FORMAT_NOT_HEX(1001, 400),
|
MESSAGE_FORMAT_NOT_HEX(1001, 400),
|
||||||
MESSAGE_BLANK(1002, 400),
|
MESSAGE_BLANK(1002, 400),
|
||||||
NO_PUBLIC_KEY(1003, 422),
|
NO_PUBLIC_KEY(1003, 422),
|
||||||
MESSAGESIZE_EXCEEDED(1004, 400);
|
MESSAGESIZE_EXCEEDED(1004, 400);
|
||||||
|
|
||||||
|
private final static Map<Integer, ApiError> map = stream(ApiError.values()).collect(toMap(apiError -> apiError.code, apiError -> apiError));
|
||||||
|
|
||||||
private final int code; // API error code
|
private final int code; // API error code
|
||||||
private final int status; // HTTP status code
|
private final int status; // HTTP status code
|
||||||
|
|
||||||
@ -117,20 +126,15 @@ public enum ApiError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ApiError fromCode(int code) {
|
public static ApiError fromCode(int code) {
|
||||||
for(ApiError apiError : ApiError.values()) {
|
return map.get(code);
|
||||||
if(apiError.code == code)
|
|
||||||
return apiError;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int getCode() {
|
public int getCode() {
|
||||||
return this.code;
|
return this.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getStatus() {
|
public int getStatus() {
|
||||||
return this.status;
|
return this.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -7,15 +7,16 @@ import javax.xml.bind.annotation.XmlAccessorType;
|
|||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class ApiErrorMessage {
|
public class ApiErrorMessage {
|
||||||
|
|
||||||
public int error;
|
protected int error;
|
||||||
|
|
||||||
public String message;
|
protected String message;
|
||||||
|
|
||||||
ApiErrorMessage() {
|
protected ApiErrorMessage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiErrorMessage(int errorCode, String message) {
|
public ApiErrorMessage(int errorCode, String message) {
|
||||||
this.error = errorCode;
|
this.error = errorCode;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
18
src/main/java/api/ApiErrors.java
Normal file
18
src/main/java/api/ApiErrors.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package api;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This annotation lists potential ApiErrors that may be returned, or thrown, during the execution of this method.
|
||||||
|
* <p>
|
||||||
|
* Value is expected to be an array of ApiError enum instances.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Target({ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ApiErrors {
|
||||||
|
ApiError[] value() default {};
|
||||||
|
}
|
@ -6,14 +6,16 @@ import javax.ws.rs.core.Response;
|
|||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
|
||||||
public class ApiException extends WebApplicationException {
|
public class ApiException extends WebApplicationException {
|
||||||
// HTTP status code
|
|
||||||
|
|
||||||
int status;
|
private static final long serialVersionUID = 4619299036312089050L;
|
||||||
|
|
||||||
|
// HTTP status code
|
||||||
|
public int status;
|
||||||
|
|
||||||
// API error code
|
// API error code
|
||||||
int error;
|
public int error;
|
||||||
|
|
||||||
String message;
|
public String message;
|
||||||
|
|
||||||
public ApiException(int status, int error, String message) {
|
public ApiException(int status, int error, String message) {
|
||||||
this(status, error, message, null);
|
this(status, error, message, null);
|
20
src/main/java/api/ApiExceptionFactory.java
Normal file
20
src/main/java/api/ApiExceptionFactory.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package api;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import globalization.Translator;
|
||||||
|
|
||||||
|
public enum ApiExceptionFactory {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
public ApiException createException(HttpServletRequest request, ApiError apiError, Throwable throwable, Object... args) {
|
||||||
|
String template = Translator.INSTANCE.translate("ApiError", request.getLocale().getLanguage(), apiError.name());
|
||||||
|
String message = String.format(template, args);
|
||||||
|
return new ApiException(apiError.getStatus(), apiError.getCode(), message, throwable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ApiException createException(HttpServletRequest request, ApiError apiError) {
|
||||||
|
return createException(request, apiError, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,8 +2,6 @@ package api;
|
|||||||
|
|
||||||
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
|
import org.eclipse.jetty.rewrite.handler.RedirectPatternRule;
|
||||||
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
|
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
|
||||||
|
|
||||||
@ -17,30 +15,21 @@ import org.eclipse.jetty.servlets.CrossOriginFilter;
|
|||||||
import org.glassfish.jersey.server.ResourceConfig;
|
import org.glassfish.jersey.server.ResourceConfig;
|
||||||
import org.glassfish.jersey.servlet.ServletContainer;
|
import org.glassfish.jersey.servlet.ServletContainer;
|
||||||
|
|
||||||
|
import api.resource.AnnotationPostProcessor;
|
||||||
|
import api.resource.ApiDefinition;
|
||||||
import settings.Settings;
|
import settings.Settings;
|
||||||
|
|
||||||
public class ApiService {
|
public class ApiService {
|
||||||
|
|
||||||
private final Server server;
|
private final Server server;
|
||||||
private final Set<Class<?>> resources;
|
private final ResourceConfig config;
|
||||||
|
|
||||||
public ApiService() {
|
public ApiService() {
|
||||||
// Resources to register
|
config = new ResourceConfig();
|
||||||
this.resources = new HashSet<Class<?>>();
|
config.packages("api.resource");
|
||||||
this.resources.add(AddressesResource.class);
|
config.register(OpenApiResource.class);
|
||||||
this.resources.add(AdminResource.class);
|
config.register(ApiDefinition.class);
|
||||||
this.resources.add(AssetsResource.class);
|
config.register(AnnotationPostProcessor.class);
|
||||||
this.resources.add(BlocksResource.class);
|
|
||||||
this.resources.add(NamesResource.class);
|
|
||||||
this.resources.add(PaymentsResource.class);
|
|
||||||
this.resources.add(TransactionsResource.class);
|
|
||||||
this.resources.add(UtilsResource.class);
|
|
||||||
|
|
||||||
this.resources.add(BlockExplorerResource.class); // block-explorer.html
|
|
||||||
this.resources.add(OpenApiResource.class); // Swagger/OpenAPI
|
|
||||||
this.resources.add(ApiDefinition.class); // API info
|
|
||||||
this.resources.add(AnnotationPostProcessor.class); // For API resource annotations
|
|
||||||
ResourceConfig config = new ResourceConfig(this.resources);
|
|
||||||
|
|
||||||
// Create RPC server
|
// Create RPC server
|
||||||
this.server = new Server(Settings.getInstance().getRpcPort());
|
this.server = new Server(Settings.getInstance().getRpcPort());
|
||||||
@ -94,8 +83,9 @@ public class ApiService {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable<Class<?>> getResources() {
|
public Iterable<Class<?>> getResources() {
|
||||||
return resources;
|
// return resources;
|
||||||
|
return config.getClasses();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
@ -13,10 +13,10 @@ public class Security {
|
|||||||
try {
|
try {
|
||||||
remoteAddr = InetAddress.getByName(request.getRemoteAddr());
|
remoteAddr = InetAddress.getByName(request.getRemoteAddr());
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.UNAUTHORIZED);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!remoteAddr.isLoopbackAddress())
|
if (!remoteAddr.isLoopbackAddress())
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.UNAUTHORIZED);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,7 @@
|
|||||||
package api;
|
package api.resource;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
|
||||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@ -21,6 +19,10 @@ import javax.ws.rs.Produces;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import api.ApiError;
|
||||||
|
import api.ApiErrors;
|
||||||
|
import api.ApiException;
|
||||||
|
import api.ApiExceptionFactory;
|
||||||
import data.account.AccountBalanceData;
|
import data.account.AccountBalanceData;
|
||||||
import data.account.AccountData;
|
import data.account.AccountData;
|
||||||
import qora.account.Account;
|
import qora.account.Account;
|
||||||
@ -32,12 +34,8 @@ import repository.RepositoryManager;
|
|||||||
import transform.Transformer;
|
import transform.Transformer;
|
||||||
import utils.Base58;
|
import utils.Base58;
|
||||||
|
|
||||||
@Path("addresses")
|
@Path("/addresses")
|
||||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="/Api/AddressesResource")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@Tag(name = "Addresses")
|
@Tag(name = "Addresses")
|
||||||
public class AddressesResource {
|
public class AddressesResource {
|
||||||
|
|
||||||
@ -49,30 +47,17 @@ public class AddressesResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch reference for next transaction to be created by address",
|
summary = "Fetch reference for next transaction to be created by address",
|
||||||
description = "Returns the base58-encoded signature of the last confirmed transaction created by address, failing that: the first incoming transaction to address. Returns \"false\" if there is no transactions.",
|
description = "Returns the base58-encoded signature of the last confirmed transaction created by address, failing that: the first incoming transaction to address. Returns \"false\" if there is no transactions.",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="GET lastreference:address"),
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
}),
|
|
||||||
@Extension(properties = {
|
|
||||||
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the base58-encoded transaction signature or \"false\"",
|
description = "the base58-encoded transaction signature or \"false\"",
|
||||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
public String getLastReference(@Parameter(ref = "address") @PathParam("address") String address) {
|
public String getLastReference(@Parameter(ref = "address") @PathParam("address") String address) {
|
||||||
if (!Crypto.isValidAddress(address))
|
if (!Crypto.isValidAddress(address))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
byte[] lastReference = null;
|
byte[] lastReference = null;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -81,7 +66,7 @@ public class AddressesResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(lastReference == null || lastReference.length == 0) {
|
if(lastReference == null || lastReference.length == 0) {
|
||||||
@ -95,31 +80,18 @@ public class AddressesResource {
|
|||||||
@Path("/lastreference/{address}/unconfirmed")
|
@Path("/lastreference/{address}/unconfirmed")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch reference for next transaction to be created by address, considering unconfirmed transactions",
|
summary = "Fetch reference for next transaction to be created by address, considering unconfirmed transactions",
|
||||||
description = "Returns the base58-encoded signature of the last confirmed/unconfirmed transaction created by address, failing that: the first incoming transaction. Returns \\\"false\\\" if there is no transactions.",
|
description = "Returns the base58-encoded signature of the last confirmed/unconfirmed transaction created by address, failing that: the first incoming transaction. Returns \"false\" if there is no transactions.",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="GET lastreference:address:unconfirmed"),
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
}),
|
|
||||||
@Extension(properties = {
|
|
||||||
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the base58-encoded transaction signature",
|
description = "the base58-encoded transaction signature",
|
||||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
public String getLastReferenceUnconfirmed(@PathParam("address") String address) {
|
public String getLastReferenceUnconfirmed(@PathParam("address") String address) {
|
||||||
if (!Crypto.isValidAddress(address))
|
if (!Crypto.isValidAddress(address))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
byte[] lastReference = null;
|
byte[] lastReference = null;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -128,7 +100,7 @@ public class AddressesResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(lastReference == null || lastReference.length == 0) {
|
if(lastReference == null || lastReference.length == 0) {
|
||||||
@ -143,21 +115,9 @@ public class AddressesResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Validates the given address",
|
summary = "Validates the given address",
|
||||||
description = "Returns true/false.",
|
description = "Returns true/false.",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="GET validate:address"),
|
|
||||||
@ExtensionProperty(name="summary.key", value="operation:summary"),
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description"),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean")),
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -170,30 +130,17 @@ public class AddressesResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Return the generating balance of the given address",
|
summary = "Return the generating balance of the given address",
|
||||||
description = "Returns the effective balance of the given address, used in Proof-of-Stake calculationgs when generating a new block.",
|
description = "Returns the effective balance of the given address, used in Proof-of-Stake calculationgs when generating a new block.",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="GET generatingbalance:address"),
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
}),
|
|
||||||
@Extension(properties = {
|
|
||||||
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the generating balance",
|
description = "the generating balance",
|
||||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")),
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
public BigDecimal getGeneratingBalanceOfAddress(@PathParam("address") String address) {
|
public BigDecimal getGeneratingBalanceOfAddress(@PathParam("address") String address) {
|
||||||
if (!Crypto.isValidAddress(address))
|
if (!Crypto.isValidAddress(address))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Account account = new Account(repository, address);
|
Account account = new Account(repository, address);
|
||||||
@ -201,7 +148,7 @@ public class AddressesResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,30 +156,17 @@ public class AddressesResource {
|
|||||||
@Path("/balance/{address}")
|
@Path("/balance/{address}")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Returns the confirmed balance of the given address",
|
summary = "Returns the confirmed balance of the given address",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="GET balance:address"),
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
}),
|
|
||||||
@Extension(properties = {
|
|
||||||
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the balance",
|
description = "the balance",
|
||||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")),
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
public BigDecimal getGeneratingBalance(@PathParam("address") String address) {
|
public BigDecimal getGeneratingBalance(@PathParam("address") String address) {
|
||||||
if (!Crypto.isValidAddress(address))
|
if (!Crypto.isValidAddress(address))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Account account = new Account(repository, address);
|
Account account = new Account(repository, address);
|
||||||
@ -240,7 +174,7 @@ public class AddressesResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,30 +183,17 @@ public class AddressesResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Asset-specific balance request",
|
summary = "Asset-specific balance request",
|
||||||
description = "Returns the confirmed balance of the given address for the given asset key.",
|
description = "Returns the confirmed balance of the given address for the given asset key.",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="GET assetbalance:assetid:address"),
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
}),
|
|
||||||
@Extension(properties = {
|
|
||||||
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\", \"INVALID_ASSET_ID\"]", parseValue = true),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the balance",
|
description = "the balance",
|
||||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number")),
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string", format = "number"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
public BigDecimal getAssetBalance(@PathParam("assetid") long assetid, @PathParam("address") String address) {
|
public BigDecimal getAssetBalance(@PathParam("assetid") long assetid, @PathParam("address") String address) {
|
||||||
if (!Crypto.isValidAddress(address))
|
if (!Crypto.isValidAddress(address))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Account account = new Account(repository, address);
|
Account account = new Account(repository, address);
|
||||||
@ -280,7 +201,7 @@ public class AddressesResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,37 +210,24 @@ public class AddressesResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "All assets owned by this address",
|
summary = "All assets owned by this address",
|
||||||
description = "Returns the list of assets for this address, with balances.",
|
description = "Returns the list of assets for this address, with balances.",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="GET assets:address"),
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
}),
|
|
||||||
@Extension(properties = {
|
|
||||||
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the list of assets",
|
description = "the list of assets",
|
||||||
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AccountBalanceData.class))),
|
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AccountBalanceData.class)))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
public List<AccountBalanceData> getAssets(@PathParam("address") String address) {
|
public List<AccountBalanceData> getAssets(@PathParam("address") String address) {
|
||||||
if (!Crypto.isValidAddress(address))
|
if (!Crypto.isValidAddress(address))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
return repository.getAccountRepository().getAllBalances(address);
|
return repository.getAccountRepository().getAllBalances(address);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,24 +235,10 @@ public class AddressesResource {
|
|||||||
@Path("/balance/{address}/{confirmations}")
|
@Path("/balance/{address}/{confirmations}")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Calculates the balance of the given address for the given confirmations",
|
summary = "Calculates the balance of the given address for the given confirmations",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="GET balance:address:confirmations"),
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
}),
|
|
||||||
@Extension(properties = {
|
|
||||||
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the balance",
|
description = "the balance",
|
||||||
content = @Content(schema = @Schema(implementation = String.class)),
|
content = @Content(schema = @Schema(type = "string", format = "number"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -357,30 +251,17 @@ public class AddressesResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Get public key of address",
|
summary = "Get public key of address",
|
||||||
description = "Returns the base58-encoded account public key of the given address, or \"false\" if address not known or has no public key.",
|
description = "Returns the base58-encoded account public key of the given address, or \"false\" if address not known or has no public key.",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="GET publickey:address"),
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
}),
|
|
||||||
@Extension(properties = {
|
|
||||||
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the public key",
|
description = "the public key",
|
||||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||||
public String getPublicKey(@PathParam("address") String address) {
|
public String getPublicKey(@PathParam("address") String address) {
|
||||||
if (!Crypto.isValidAddress(address))
|
if (!Crypto.isValidAddress(address))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ADDRESS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
AccountData accountData = repository.getAccountRepository().getAccount(address);
|
||||||
@ -396,7 +277,7 @@ public class AddressesResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,46 +286,33 @@ public class AddressesResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert public key into address",
|
summary = "Convert public key into address",
|
||||||
description = "Returns account address based on supplied public key. Expects base58-encoded, 32-byte public key.",
|
description = "Returns account address based on supplied public key. Expects base58-encoded, 32-byte public key.",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="GET publickey:address"),
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
}),
|
|
||||||
@Extension(properties = {
|
|
||||||
@ExtensionProperty(name="apiErrors", value="[\"INVALID_ADDRESS\"]", parseValue = true),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the address",
|
description = "the address",
|
||||||
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")),
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_PUBLIC_KEY, ApiError.REPOSITORY_ISSUE})
|
||||||
public String fromPublicKey(@PathParam("publickey") String publicKey58) {
|
public String fromPublicKey(@PathParam("publickey") String publicKey58) {
|
||||||
// Decode public key
|
// Decode public key
|
||||||
byte[] publicKey;
|
byte[] publicKey;
|
||||||
try {
|
try {
|
||||||
publicKey = Base58.decode(publicKey58);
|
publicKey = Base58.decode(publicKey58);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_PUBLIC_KEY, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Correct size for public key?
|
// Correct size for public key?
|
||||||
if (publicKey.length != Transformer.PUBLIC_KEY_LENGTH)
|
if (publicKey.length != Transformer.PUBLIC_KEY_LENGTH)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_PUBLIC_KEY);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
return Crypto.toAddress(publicKey);
|
return Crypto.toAddress(publicKey);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,8 @@
|
|||||||
package api;
|
package api.resource;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
|
||||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
@ -17,14 +15,11 @@ import javax.ws.rs.Produces;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import api.Security;
|
||||||
import controller.Controller;
|
import controller.Controller;
|
||||||
|
|
||||||
@Path("admin")
|
@Path("/admin")
|
||||||
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="path", value="/Api/AdminResource")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@Tag(name = "Admin")
|
@Tag(name = "Admin")
|
||||||
public class AdminResource {
|
public class AdminResource {
|
||||||
|
|
||||||
@ -52,25 +47,15 @@ public class AdminResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch running time of server",
|
summary = "Fetch running time of server",
|
||||||
description = "Returns uptime in milliseconds",
|
description = "Returns uptime in milliseconds",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "uptime in milliseconds",
|
description = "uptime in milliseconds",
|
||||||
content = @Content(schema = @Schema(implementation = String.class)),
|
content = @Content(schema = @Schema(type = "number"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public String uptime() {
|
public long uptime() {
|
||||||
return Long.toString(System.currentTimeMillis() - Controller.startTime);
|
return System.currentTimeMillis() - Controller.startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@ -78,20 +63,10 @@ public class AdminResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Shutdown",
|
summary = "Shutdown",
|
||||||
description = "Shutdown",
|
description = "Shutdown",
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="operation:description")
|
|
||||||
})
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "\"true\"",
|
description = "\"true\"",
|
||||||
content = @Content(schema = @Schema(implementation = String.class)),
|
content = @Content(schema = @Schema(type = "string"))
|
||||||
extensions = {
|
|
||||||
@Extension(name = "translation", properties = {
|
|
||||||
@ExtensionProperty(name="description.key", value="success_response:description")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
114
src/main/java/api/resource/AnnotationPostProcessor.java
Normal file
114
src/main/java/api/resource/AnnotationPostProcessor.java
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package api.resource;
|
||||||
|
|
||||||
|
import globalization.Translator;
|
||||||
|
import io.swagger.v3.core.converter.ModelConverters;
|
||||||
|
import io.swagger.v3.jaxrs2.Reader;
|
||||||
|
import io.swagger.v3.jaxrs2.ReaderListener;
|
||||||
|
import io.swagger.v3.oas.models.Components;
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.Operation;
|
||||||
|
import io.swagger.v3.oas.models.PathItem;
|
||||||
|
import io.swagger.v3.oas.models.examples.Example;
|
||||||
|
import io.swagger.v3.oas.models.media.Content;
|
||||||
|
import io.swagger.v3.oas.models.media.MediaType;
|
||||||
|
import io.swagger.v3.oas.models.media.Schema;
|
||||||
|
import io.swagger.v3.oas.models.parameters.Parameter;
|
||||||
|
import io.swagger.v3.oas.models.responses.ApiResponse;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import api.ApiError;
|
||||||
|
import api.ApiErrorMessage;
|
||||||
|
import api.ApiErrors;
|
||||||
|
import api.ApiService;
|
||||||
|
|
||||||
|
public class AnnotationPostProcessor implements ReaderListener {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(AnnotationPostProcessor.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeScan(Reader reader, OpenAPI openAPI) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterScan(Reader reader, OpenAPI openAPI) {
|
||||||
|
// Populate Components section with reusable parameters, like "limit" and "offset"
|
||||||
|
// We take the reusable parameters from AdminResource.globalParameters path "/admin/unused"
|
||||||
|
Components components = openAPI.getComponents();
|
||||||
|
|
||||||
|
PathItem globalParametersPathItem = openAPI.getPaths().get("/admin/unused");
|
||||||
|
|
||||||
|
if (globalParametersPathItem != null) {
|
||||||
|
for (Parameter parameter : globalParametersPathItem.getGet().getParameters())
|
||||||
|
components.addParameters(parameter.getName(), parameter);
|
||||||
|
|
||||||
|
openAPI.getPaths().remove("/admin/unused");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search all ApiService resources (classes) for @ApiErrors annotations
|
||||||
|
// to generate corresponding openAPI operation responses.
|
||||||
|
for (Class<?> clazz : ApiService.getInstance().getResources()) {
|
||||||
|
Path classPath = clazz.getAnnotation(Path.class);
|
||||||
|
if (classPath == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String classPathString = classPath.value();
|
||||||
|
if (classPathString.charAt(0) != '/')
|
||||||
|
classPathString = "/" + classPathString;
|
||||||
|
|
||||||
|
for (Method method : clazz.getDeclaredMethods()) {
|
||||||
|
ApiErrors apiErrors = method.getAnnotation(ApiErrors.class);
|
||||||
|
if (apiErrors == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
LOGGER.info("Found @ApiErrors annotation on " + clazz.getSimpleName() + "." + method.getName());
|
||||||
|
PathItem pathItem = getPathItemFromMethod(openAPI, classPathString, method);
|
||||||
|
|
||||||
|
for (Operation operation : pathItem.readOperations())
|
||||||
|
for (ApiError apiError : apiErrors.value())
|
||||||
|
addApiErrorResponse(operation, apiError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PathItem getPathItemFromMethod(OpenAPI openAPI, String classPathString, Method method) {
|
||||||
|
Path path = method.getAnnotation(Path.class);
|
||||||
|
if (path == null)
|
||||||
|
throw new RuntimeException("API method has no @Path annotation?");
|
||||||
|
|
||||||
|
String pathString = path.value();
|
||||||
|
return openAPI.getPaths().get(classPathString + pathString);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addApiErrorResponse(Operation operation, ApiError apiError) {
|
||||||
|
String statusCode = Integer.toString(apiError.getStatus()) + " " + apiError.name();
|
||||||
|
|
||||||
|
// Create response for this HTTP response code if it doesn't already exist
|
||||||
|
ApiResponse apiResponse = operation.getResponses().get(statusCode);
|
||||||
|
if (apiResponse == null) {
|
||||||
|
Schema<?> errorMessageSchema = ModelConverters.getInstance().readAllAsResolvedSchema(ApiErrorMessage.class).schema;
|
||||||
|
MediaType mediaType = new MediaType().schema(errorMessageSchema);
|
||||||
|
Content content = new Content().addMediaType(javax.ws.rs.core.MediaType.APPLICATION_JSON, mediaType);
|
||||||
|
apiResponse = new ApiResponse().content(content);
|
||||||
|
operation.getResponses().addApiResponse(statusCode, apiResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this specific ApiError code as an example
|
||||||
|
int apiErrorCode = apiError.getCode();
|
||||||
|
String lang = Locale.getDefault().getLanguage();
|
||||||
|
ApiErrorMessage apiErrorMessage = new ApiErrorMessage(apiErrorCode, Translator.INSTANCE.translate("ApiError", lang, apiError.name()));
|
||||||
|
Example example = new Example().value(apiErrorMessage);
|
||||||
|
|
||||||
|
// XXX: addExamples(..) is not working in Swagger 2.0.4. This bug is referenced in https://github.com/swagger-api/swagger-ui/issues/2651
|
||||||
|
// Replace the call to .setExample(..) by .addExamples(..) when the bug is fixed.
|
||||||
|
apiResponse.getContent().get(javax.ws.rs.core.MediaType.APPLICATION_JSON).setExample(example);
|
||||||
|
//apiResponse.getContent().get(javax.ws.rs.core.MediaType.APPLICATION_JSON).addExamples(Integer.toString(apiErrorCode), example);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package api;
|
package api.resource;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
import io.swagger.v3.oas.annotations.extensions.Extension;
|
||||||
@ -25,5 +25,4 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
public class ApiDefinition {
|
public class ApiDefinition {
|
||||||
|
}
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package api;
|
package api.resource;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
@ -30,6 +30,9 @@ import javax.ws.rs.QueryParam;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import api.ApiError;
|
||||||
|
import api.ApiErrors;
|
||||||
|
import api.ApiExceptionFactory;
|
||||||
import api.models.AssetWithHolders;
|
import api.models.AssetWithHolders;
|
||||||
import api.models.OrderWithTrades;
|
import api.models.OrderWithTrades;
|
||||||
import api.models.TradeWithOrderInfo;
|
import api.models.TradeWithOrderInfo;
|
||||||
@ -58,6 +61,7 @@ public class AssetsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
public List<AssetData> getAllAssets(@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
public List<AssetData> getAllAssets(@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
List<AssetData> assets = repository.getAssetRepository().getAllAssets();
|
List<AssetData> assets = repository.getAssetRepository().getAllAssets();
|
||||||
@ -69,7 +73,7 @@ public class AssetsResource {
|
|||||||
|
|
||||||
return assets;
|
return assets;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,9 +89,10 @@ public class AssetsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE})
|
||||||
public AssetWithHolders getAssetInfo(@QueryParam("assetId") Integer assetId, @QueryParam("assetName") String assetName, @Parameter(ref = "includeHolders") @QueryParam("includeHolders") boolean includeHolders) {
|
public AssetWithHolders getAssetInfo(@QueryParam("assetId") Integer assetId, @QueryParam("assetName") String assetName, @Parameter(ref = "includeHolders") @QueryParam("includeHolders") boolean includeHolders) {
|
||||||
if (assetId == null && (assetName == null || assetName.isEmpty()))
|
if (assetId == null && (assetName == null || assetName.isEmpty()))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
AssetData assetData = null;
|
AssetData assetData = null;
|
||||||
@ -98,7 +103,7 @@ public class AssetsResource {
|
|||||||
assetData = repository.getAssetRepository().fromAssetName(assetName);
|
assetData = repository.getAssetRepository().fromAssetName(assetName);
|
||||||
|
|
||||||
if (assetData == null)
|
if (assetData == null)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
|
|
||||||
List<AccountBalanceData> holders = null;
|
List<AccountBalanceData> holders = null;
|
||||||
if (includeHolders)
|
if (includeHolders)
|
||||||
@ -106,7 +111,7 @@ public class AssetsResource {
|
|||||||
|
|
||||||
return new AssetWithHolders(assetData, holders);
|
return new AssetWithHolders(assetData, holders);
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,14 +127,15 @@ public class AssetsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE})
|
||||||
public List<OrderData> getAssetOrders(@Parameter(ref = "assetId") @PathParam("assetId") int assetId, @Parameter(ref = "otherAssetId") @PathParam("otherAssetId") int otherAssetId,
|
public List<OrderData> getAssetOrders(@Parameter(ref = "assetId") @PathParam("assetId") int assetId, @Parameter(ref = "otherAssetId") @PathParam("otherAssetId") int otherAssetId,
|
||||||
@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
if (!repository.getAssetRepository().assetExists(assetId))
|
if (!repository.getAssetRepository().assetExists(assetId))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
|
|
||||||
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
|
|
||||||
List<OrderData> orders = repository.getAssetRepository().getOpenOrders(assetId, otherAssetId);
|
List<OrderData> orders = repository.getAssetRepository().getOpenOrders(assetId, otherAssetId);
|
||||||
|
|
||||||
@ -140,7 +146,7 @@ public class AssetsResource {
|
|||||||
|
|
||||||
return orders;
|
return orders;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,14 +164,15 @@ public class AssetsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ASSET_ID, ApiError.REPOSITORY_ISSUE})
|
||||||
public List<TradeWithOrderInfo> getAssetTrades(@Parameter(ref = "assetId") @PathParam("assetId") int assetId, @Parameter(ref = "otherAssetId") @PathParam("otherAssetId") int otherAssetId,
|
public List<TradeWithOrderInfo> getAssetTrades(@Parameter(ref = "assetId") @PathParam("assetId") int assetId, @Parameter(ref = "otherAssetId") @PathParam("otherAssetId") int otherAssetId,
|
||||||
@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
@Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
if (!repository.getAssetRepository().assetExists(assetId))
|
if (!repository.getAssetRepository().assetExists(assetId))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
|
|
||||||
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
if (!repository.getAssetRepository().assetExists(otherAssetId))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ASSET_ID);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ASSET_ID);
|
||||||
|
|
||||||
List<TradeData> trades = repository.getAssetRepository().getTrades(assetId, otherAssetId);
|
List<TradeData> trades = repository.getAssetRepository().getTrades(assetId, otherAssetId);
|
||||||
|
|
||||||
@ -184,7 +191,7 @@ public class AssetsResource {
|
|||||||
|
|
||||||
return fullTrades;
|
return fullTrades;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,25 +207,26 @@ public class AssetsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_ORDER_ID, ApiError.ORDER_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
public OrderWithTrades getAssetOrder(@PathParam("orderId") String orderId58) {
|
public OrderWithTrades getAssetOrder(@PathParam("orderId") String orderId58) {
|
||||||
// Decode orderID
|
// Decode orderID
|
||||||
byte[] orderId;
|
byte[] orderId;
|
||||||
try {
|
try {
|
||||||
orderId = Base58.decode(orderId58);
|
orderId = Base58.decode(orderId58);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_ORDER_ID, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ORDER_ID, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
OrderData orderData = repository.getAssetRepository().fromOrderId(orderId);
|
OrderData orderData = repository.getAssetRepository().fromOrderId(orderId);
|
||||||
if (orderData == null)
|
if (orderData == null)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.ORDER_NO_EXISTS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ORDER_NO_EXISTS);
|
||||||
|
|
||||||
List<TradeData> trades = repository.getAssetRepository().getOrdersTrades(orderId);
|
List<TradeData> trades = repository.getAssetRepository().getOrdersTrades(orderId);
|
||||||
|
|
||||||
return new OrderWithTrades(orderData, trades);
|
return new OrderWithTrades(orderData, trades);
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,20 +253,21 @@ public class AssetsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE, ApiError.TRANSACTION_INVALID})
|
||||||
public String issueAsset(IssueAssetTransactionData transactionData) {
|
public String issueAsset(IssueAssetTransactionData transactionData) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
ValidationResult result = transaction.isValid();
|
ValidationResult result = transaction.isValid();
|
||||||
if (result != ValidationResult.OK)
|
if (result != ValidationResult.OK)
|
||||||
throw new ApiException(400, ApiError.INVALID_DATA.getCode(), "Transaction invalid: " + result.name());
|
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
byte[] bytes = IssueAssetTransactionTransformer.toBytes(transactionData);
|
byte[] bytes = IssueAssetTransactionTransformer.toBytes(transactionData);
|
||||||
return Base58.encode(bytes);
|
return Base58.encode(bytes);
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.UNKNOWN, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
37
src/main/java/api/resource/BlockExplorerResource.java
Normal file
37
src/main/java/api/resource/BlockExplorerResource.java
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package api.resource;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
|
||||||
|
@Path("/")
|
||||||
|
public class BlockExplorerResource {
|
||||||
|
|
||||||
|
@Context
|
||||||
|
HttpServletRequest request;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/block-explorer.html")
|
||||||
|
@Operation(hidden = true)
|
||||||
|
public String getBlockExplorer() {
|
||||||
|
ClassLoader loader = this.getClass().getClassLoader();
|
||||||
|
try (InputStream inputStream = loader.getResourceAsStream("block-explorer.html")) {
|
||||||
|
if (inputStream == null)
|
||||||
|
return "block-explorer.html resource not found";
|
||||||
|
|
||||||
|
return new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.joining("\n"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
return "Error reading block-explorer.html resource";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,11 +1,9 @@
|
|||||||
package api;
|
package api.resource;
|
||||||
|
|
||||||
import data.block.BlockData;
|
import data.block.BlockData;
|
||||||
import data.transaction.TransactionData;
|
import data.transaction.TransactionData;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
|
||||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
@ -26,6 +24,10 @@ import javax.ws.rs.QueryParam;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import api.ApiError;
|
||||||
|
import api.ApiErrors;
|
||||||
|
import api.ApiException;
|
||||||
|
import api.ApiExceptionFactory;
|
||||||
import api.models.BlockWithTransactions;
|
import api.models.BlockWithTransactions;
|
||||||
import qora.block.Block;
|
import qora.block.Block;
|
||||||
import repository.DataException;
|
import repository.DataException;
|
||||||
@ -33,22 +35,9 @@ import repository.Repository;
|
|||||||
import repository.RepositoryManager;
|
import repository.RepositoryManager;
|
||||||
import utils.Base58;
|
import utils.Base58;
|
||||||
|
|
||||||
@Path("blocks")
|
@Path("/blocks")
|
||||||
@Produces({
|
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||||
MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN
|
@Tag(name = "Blocks")
|
||||||
})
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "/Api/BlocksResource"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@Tag(
|
|
||||||
name = "Blocks"
|
|
||||||
)
|
|
||||||
public class BlocksResource {
|
public class BlocksResource {
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
@ -59,28 +48,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch block using base58 signature",
|
summary = "Fetch block using base58 signature",
|
||||||
description = "Returns the block that matches the given signature",
|
description = "Returns the block that matches the given signature",
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET signature"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
), @Extension(
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "apiErrors",
|
|
||||||
value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]",
|
|
||||||
parseValue = true
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the block",
|
description = "the block",
|
||||||
@ -88,30 +55,18 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockWithTransactions.class
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public BlockWithTransactions getBlock(@PathParam("signature") String signature58, @Parameter(
|
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
ref = "includeTransactions"
|
public BlockWithTransactions getBlock(@PathParam("signature") String signature58, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||||
) @QueryParam("includeTransactions") boolean includeTransactions) {
|
|
||||||
// Decode signature
|
// Decode signature
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
signature = Base58.decode(signature58);
|
signature = Base58.decode(signature58);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -120,7 +75,7 @@ public class BlocksResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,18 +84,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch genesis block",
|
summary = "Fetch genesis block",
|
||||||
description = "Returns the genesis block",
|
description = "Returns the genesis block",
|
||||||
extensions = @Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET first"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the block",
|
description = "the block",
|
||||||
@ -148,31 +91,19 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockWithTransactions.class
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public BlockWithTransactions getFirstBlock(@Parameter(
|
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
ref = "includeTransactions"
|
public BlockWithTransactions getFirstBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||||
) @QueryParam("includeTransactions") boolean includeTransactions) {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().fromHeight(1);
|
BlockData blockData = repository.getBlockRepository().fromHeight(1);
|
||||||
return packageBlockData(repository, blockData, includeTransactions);
|
return packageBlockData(repository, blockData, includeTransactions);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,18 +112,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch last/newest block in blockchain",
|
summary = "Fetch last/newest block in blockchain",
|
||||||
description = "Returns the last valid block",
|
description = "Returns the last valid block",
|
||||||
extensions = @Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET last"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the block",
|
description = "the block",
|
||||||
@ -200,31 +119,19 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockWithTransactions.class
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public BlockWithTransactions getLastBlock(@Parameter(
|
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
ref = "includeTransactions"
|
public BlockWithTransactions getLastBlock(@Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||||
) @QueryParam("includeTransactions") boolean includeTransactions) {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||||
return packageBlockData(repository, blockData, includeTransactions);
|
return packageBlockData(repository, blockData, includeTransactions);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,28 +140,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch child block using base58 signature of parent block",
|
summary = "Fetch child block using base58 signature of parent block",
|
||||||
description = "Returns the child block of the block that matches the given signature",
|
description = "Returns the child block of the block that matches the given signature",
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET child:signature"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
), @Extension(
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "apiErrors",
|
|
||||||
value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]",
|
|
||||||
parseValue = true
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the block",
|
description = "the block",
|
||||||
@ -262,30 +147,18 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockWithTransactions.class
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public BlockWithTransactions getChild(@PathParam("signature") String signature58, @Parameter(
|
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
ref = "includeTransactions"
|
public BlockWithTransactions getChild(@PathParam("signature") String signature58, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||||
) @QueryParam("includeTransactions") boolean includeTransactions) {
|
|
||||||
// Decode signature
|
// Decode signature
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
signature = Base58.decode(signature58);
|
signature = Base58.decode(signature58);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -293,7 +166,7 @@ public class BlocksResource {
|
|||||||
|
|
||||||
// Check block exists
|
// Check block exists
|
||||||
if (blockData == null)
|
if (blockData == null)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||||
|
|
||||||
BlockData childBlockData = repository.getBlockRepository().fromReference(signature);
|
BlockData childBlockData = repository.getBlockRepository().fromReference(signature);
|
||||||
|
|
||||||
@ -302,7 +175,7 @@ public class BlocksResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -311,18 +184,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Generating balance of next block",
|
summary = "Generating balance of next block",
|
||||||
description = "Calculates the generating balance of the block that will follow the last block",
|
description = "Calculates the generating balance of the block that will follow the last block",
|
||||||
extensions = @Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET generatingbalance"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the generating balance",
|
description = "the generating balance",
|
||||||
@ -331,21 +192,11 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BigDecimal.class
|
implementation = BigDecimal.class
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
public BigDecimal getGeneratingBalance() {
|
public BigDecimal getGeneratingBalance() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||||
@ -354,7 +205,7 @@ public class BlocksResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,28 +214,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Generating balance of block after specific block",
|
summary = "Generating balance of block after specific block",
|
||||||
description = "Calculates the generating balance of the block that will follow the block that matches the signature",
|
description = "Calculates the generating balance of the block that will follow the block that matches the signature",
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET generatingbalance:signature"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
), @Extension(
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "apiErrors",
|
|
||||||
value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]",
|
|
||||||
parseValue = true
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the block",
|
description = "the block",
|
||||||
@ -393,28 +222,18 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BigDecimal.class
|
implementation = BigDecimal.class
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
public BigDecimal getGeneratingBalance(@PathParam("signature") String signature58) {
|
public BigDecimal getGeneratingBalance(@PathParam("signature") String signature58) {
|
||||||
// Decode signature
|
// Decode signature
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
signature = Base58.decode(signature58);
|
signature = Base58.decode(signature58);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -422,14 +241,14 @@ public class BlocksResource {
|
|||||||
|
|
||||||
// Check block exists
|
// Check block exists
|
||||||
if (blockData == null)
|
if (blockData == null)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||||
|
|
||||||
Block block = new Block(repository, blockData);
|
Block block = new Block(repository, blockData);
|
||||||
return block.calcNextBlockGeneratingBalance();
|
return block.calcNextBlockGeneratingBalance();
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -438,42 +257,19 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Estimated time to forge next block",
|
summary = "Estimated time to forge next block",
|
||||||
description = "Calculates the time it should take for the network to generate the next block",
|
description = "Calculates the time it should take for the network to generate the next block",
|
||||||
extensions = @Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET time"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the time in seconds", // in
|
description = "the time in seconds",
|
||||||
// seconds?
|
|
||||||
content = @Content(
|
content = @Content(
|
||||||
mediaType = MediaType.TEXT_PLAIN,
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
type = "number"
|
type = "number"
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
public long getTimePerBlock() {
|
public long getTimePerBlock() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
BlockData blockData = repository.getBlockRepository().getLastBlock();
|
||||||
@ -481,7 +277,7 @@ public class BlocksResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,18 +286,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Estimated time to forge block given generating balance",
|
summary = "Estimated time to forge block given generating balance",
|
||||||
description = "Calculates the time it should take for the network to generate blocks based on specified generating balance",
|
description = "Calculates the time it should take for the network to generate blocks based on specified generating balance",
|
||||||
extensions = @Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET time:generatingbalance"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the time", // in seconds?
|
description = "the time", // in seconds?
|
||||||
@ -510,18 +294,7 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
type = "number"
|
type = "number"
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -534,18 +307,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Current blockchain height",
|
summary = "Current blockchain height",
|
||||||
description = "Returns the block height of the last block.",
|
description = "Returns the block height of the last block.",
|
||||||
extensions = @Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET height"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the height",
|
description = "the height",
|
||||||
@ -554,28 +315,18 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
type = "number"
|
type = "number"
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
public int getHeight() {
|
public int getHeight() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
return repository.getBlockRepository().getBlockchainHeight();
|
return repository.getBlockRepository().getBlockchainHeight();
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -584,28 +335,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Height of specific block",
|
summary = "Height of specific block",
|
||||||
description = "Returns the block height of the block that matches the given signature",
|
description = "Returns the block height of the block that matches the given signature",
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET height:signature"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
), @Extension(
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "apiErrors",
|
|
||||||
value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]",
|
|
||||||
parseValue = true
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the height",
|
description = "the height",
|
||||||
@ -614,28 +343,18 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
type = "number"
|
type = "number"
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
public int getHeight(@PathParam("signature") String signature58) {
|
public int getHeight(@PathParam("signature") String signature58) {
|
||||||
// Decode signature
|
// Decode signature
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
signature = Base58.decode(signature58);
|
signature = Base58.decode(signature58);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -643,13 +362,13 @@ public class BlocksResource {
|
|||||||
|
|
||||||
// Check block exists
|
// Check block exists
|
||||||
if (blockData == null)
|
if (blockData == null)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||||
|
|
||||||
return blockData.getHeight();
|
return blockData.getHeight();
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -658,28 +377,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch block using block height",
|
summary = "Fetch block using block height",
|
||||||
description = "Returns the block with given height",
|
description = "Returns the block with given height",
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET byheight:height"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
), @Extension(
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "apiErrors",
|
|
||||||
value = "[\"BLOCK_NO_EXISTS\"]",
|
|
||||||
parseValue = true
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "the block",
|
description = "the block",
|
||||||
@ -687,31 +384,19 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockWithTransactions.class
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public BlockWithTransactions getByHeight(@PathParam("height") int height, @Parameter(
|
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
ref = "includeTransactions"
|
public BlockWithTransactions getByHeight(@PathParam("height") int height, @Parameter(ref = "includeTransactions") @QueryParam("includeTransactions") boolean includeTransactions) {
|
||||||
) @QueryParam("includeTransactions") boolean includeTransactions) {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
BlockData blockData = repository.getBlockRepository().fromHeight(height);
|
||||||
return packageBlockData(repository, blockData, includeTransactions);
|
return packageBlockData(repository, blockData, includeTransactions);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,28 +405,6 @@ public class BlocksResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch blocks starting with given height",
|
summary = "Fetch blocks starting with given height",
|
||||||
description = "Returns blocks starting with given height.",
|
description = "Returns blocks starting with given height.",
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET byheight:height"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
), @Extension(
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "apiErrors",
|
|
||||||
value = "[\"BLOCK_NO_EXISTS\"]",
|
|
||||||
parseValue = true
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "blocks",
|
description = "blocks",
|
||||||
@ -749,24 +412,12 @@ public class BlocksResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = BlockWithTransactions.class
|
implementation = BlockWithTransactions.class
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public List<BlockWithTransactions> getBlockRange(@PathParam("height") int height, @Parameter(
|
@ApiErrors({ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
ref = "count"
|
public List<BlockWithTransactions> getBlockRange(@PathParam("height") int height, @Parameter(ref = "count") @QueryParam("count") int count) {
|
||||||
) @QueryParam("count") int count) {
|
|
||||||
boolean includeTransactions = false;
|
boolean includeTransactions = false;
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -785,13 +436,25 @@ public class BlocksResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns block, optionally including transactions.
|
||||||
|
* <p>
|
||||||
|
* Throws ApiException using ApiError.BLOCK_NO_EXISTS if blockData is null.
|
||||||
|
*
|
||||||
|
* @param repository
|
||||||
|
* @param blockData
|
||||||
|
* @param includeTransactions
|
||||||
|
* @return packaged block, with optional transactions
|
||||||
|
* @throws DataException
|
||||||
|
* @throws ApiException ApiError.BLOCK_NO_EXISTS
|
||||||
|
*/
|
||||||
private BlockWithTransactions packageBlockData(Repository repository, BlockData blockData, boolean includeTransactions) throws DataException {
|
private BlockWithTransactions packageBlockData(Repository repository, BlockData blockData, boolean includeTransactions) throws DataException {
|
||||||
if (blockData == null)
|
if (blockData == null)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||||
|
|
||||||
List<TransactionData> transactions = null;
|
List<TransactionData> transactions = null;
|
||||||
if (includeTransactions) {
|
if (includeTransactions) {
|
@ -1,4 +1,4 @@
|
|||||||
package api;
|
package api.resource;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
@ -22,15 +22,14 @@ import javax.ws.rs.Produces;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import api.ApiError;
|
||||||
|
import api.ApiErrors;
|
||||||
|
import api.ApiExceptionFactory;
|
||||||
import data.transaction.RegisterNameTransactionData;
|
import data.transaction.RegisterNameTransactionData;
|
||||||
|
|
||||||
@Path("/names")
|
@Path("/names")
|
||||||
@Produces({
|
@Produces({ MediaType.TEXT_PLAIN})
|
||||||
MediaType.TEXT_PLAIN
|
@Tag(name = "Names")
|
||||||
})
|
|
||||||
@Tag(
|
|
||||||
name = "Names"
|
|
||||||
)
|
|
||||||
public class NamesResource {
|
public class NamesResource {
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
@ -61,20 +60,21 @@ public class NamesResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String buildTransaction(RegisterNameTransactionData transactionData) {
|
public String buildTransaction(RegisterNameTransactionData transactionData) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
ValidationResult result = transaction.isValid();
|
ValidationResult result = transaction.isValid();
|
||||||
if (result != ValidationResult.OK)
|
if (result != ValidationResult.OK)
|
||||||
throw new ApiException(400, ApiError.INVALID_DATA.getCode(), "Transaction invalid: " + result.name());
|
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
byte[] bytes = RegisterNameTransactionTransformer.toBytes(transactionData);
|
byte[] bytes = RegisterNameTransactionTransformer.toBytes(transactionData);
|
||||||
return Base58.encode(bytes);
|
return Base58.encode(bytes);
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.UNKNOWN, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package api;
|
package api.resource;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
@ -22,15 +22,14 @@ import javax.ws.rs.Produces;
|
|||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import api.ApiError;
|
||||||
|
import api.ApiErrors;
|
||||||
|
import api.ApiExceptionFactory;
|
||||||
import data.transaction.PaymentTransactionData;
|
import data.transaction.PaymentTransactionData;
|
||||||
|
|
||||||
@Path("/payments")
|
@Path("/payments")
|
||||||
@Produces({
|
@Produces({MediaType.TEXT_PLAIN})
|
||||||
MediaType.TEXT_PLAIN
|
@Tag(name = "Payments")
|
||||||
})
|
|
||||||
@Tag(
|
|
||||||
name = "Payments"
|
|
||||||
)
|
|
||||||
public class PaymentsResource {
|
public class PaymentsResource {
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
@ -61,20 +60,21 @@ public class PaymentsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String buildTransaction(PaymentTransactionData transactionData) {
|
public String buildTransaction(PaymentTransactionData transactionData) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
ValidationResult result = transaction.isValid();
|
ValidationResult result = transaction.isValid();
|
||||||
if (result != ValidationResult.OK)
|
if (result != ValidationResult.OK)
|
||||||
throw new ApiException(400, ApiError.INVALID_DATA.getCode(), "Transaction invalid: " + result.name());
|
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
byte[] bytes = PaymentTransactionTransformer.toBytes(transactionData);
|
byte[] bytes = PaymentTransactionTransformer.toBytes(transactionData);
|
||||||
return Base58.encode(bytes);
|
return Base58.encode(bytes);
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.UNKNOWN, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,7 @@
|
|||||||
package api;
|
package api.resource;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.extensions.Extension;
|
|
||||||
import io.swagger.v3.oas.annotations.extensions.ExtensionProperty;
|
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@ -30,10 +28,15 @@ import javax.ws.rs.core.MediaType;
|
|||||||
|
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
|
|
||||||
|
import api.ApiError;
|
||||||
|
import api.ApiErrors;
|
||||||
|
import api.ApiException;
|
||||||
|
import api.ApiExceptionFactory;
|
||||||
import api.models.SimpleTransactionSignRequest;
|
import api.models.SimpleTransactionSignRequest;
|
||||||
import data.transaction.GenesisTransactionData;
|
import data.transaction.GenesisTransactionData;
|
||||||
import data.transaction.PaymentTransactionData;
|
import data.transaction.PaymentTransactionData;
|
||||||
import data.transaction.TransactionData;
|
import data.transaction.TransactionData;
|
||||||
|
import globalization.Translator;
|
||||||
import repository.DataException;
|
import repository.DataException;
|
||||||
import repository.Repository;
|
import repository.Repository;
|
||||||
import repository.RepositoryManager;
|
import repository.RepositoryManager;
|
||||||
@ -41,22 +44,9 @@ import transform.TransformationException;
|
|||||||
import transform.transaction.TransactionTransformer;
|
import transform.transaction.TransactionTransformer;
|
||||||
import utils.Base58;
|
import utils.Base58;
|
||||||
|
|
||||||
@Path("transactions")
|
@Path("/transactions")
|
||||||
@Produces({
|
@Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
|
||||||
MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN
|
@Tag(name = "Transactions")
|
||||||
})
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "/Api/TransactionsResource"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@Tag(
|
|
||||||
name = "Transactions"
|
|
||||||
)
|
|
||||||
public class TransactionsResource {
|
public class TransactionsResource {
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
@ -67,17 +57,6 @@ public class TransactionsResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch transaction using transaction signature",
|
summary = "Fetch transaction using transaction signature",
|
||||||
description = "Returns transaction",
|
description = "Returns transaction",
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "apiErrors",
|
|
||||||
value = "[\"INVALID_SIGNATURE\", \"TRANSACTION_NO_EXISTS\"]",
|
|
||||||
parseValue = true
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "a transaction",
|
description = "a transaction",
|
||||||
@ -85,39 +64,29 @@ public class TransactionsResource {
|
|||||||
schema = @Schema(
|
schema = @Schema(
|
||||||
implementation = TransactionData.class
|
implementation = TransactionData.class
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.TRANSACTION_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
public TransactionData getTransactions(@PathParam("signature") String signature58) {
|
public TransactionData getTransactions(@PathParam("signature") String signature58) {
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
signature = Base58.decode(signature58);
|
signature = Base58.decode(signature58);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
|
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
|
||||||
if (transactionData == null)
|
if (transactionData == null)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.TRANSACTION_NO_EXISTS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_NO_EXISTS);
|
||||||
|
|
||||||
return transactionData;
|
return transactionData;
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,28 +95,6 @@ public class TransactionsResource {
|
|||||||
@Operation(
|
@Operation(
|
||||||
summary = "Fetch transactions using block signature",
|
summary = "Fetch transactions using block signature",
|
||||||
description = "Returns list of transactions",
|
description = "Returns list of transactions",
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "path",
|
|
||||||
value = "GET block:signature"
|
|
||||||
), @ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "operation:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
), @Extension(
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "apiErrors",
|
|
||||||
value = "[\"INVALID_SIGNATURE\", \"BLOCK_NO_EXISTS\"]",
|
|
||||||
parseValue = true
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
description = "list of transactions",
|
description = "list of transactions",
|
||||||
@ -159,31 +106,17 @@ public class TransactionsResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public List<TransactionData> getBlockTransactions(@PathParam("signature") String signature58, @Parameter(
|
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.BLOCK_NO_EXISTS, ApiError.REPOSITORY_ISSUE})
|
||||||
ref = "limit"
|
public List<TransactionData> getBlockTransactions(@PathParam("signature") String signature58, @Parameter(ref = "limit") @QueryParam("limit") int limit, @Parameter(ref = "offset") @QueryParam("offset") int offset) {
|
||||||
) @QueryParam("limit") int limit, @Parameter(
|
|
||||||
ref = "offset"
|
|
||||||
) @QueryParam("offset") int offset) {
|
|
||||||
byte[] signature;
|
byte[] signature;
|
||||||
try {
|
try {
|
||||||
signature = Base58.decode(signature58);
|
signature = Base58.decode(signature58);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -191,7 +124,7 @@ public class TransactionsResource {
|
|||||||
|
|
||||||
// check if block exists
|
// check if block exists
|
||||||
if (transactions == null)
|
if (transactions == null)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.BLOCK_NO_EXISTS);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_NO_EXISTS);
|
||||||
|
|
||||||
// Pagination would take effect here (or as part of the repository access)
|
// Pagination would take effect here (or as part of the repository access)
|
||||||
int fromIndex = Integer.min(offset, transactions.size());
|
int fromIndex = Integer.min(offset, transactions.size());
|
||||||
@ -202,7 +135,7 @@ public class TransactionsResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,28 +153,18 @@ public class TransactionsResource {
|
|||||||
implementation = TransactionData.class
|
implementation = TransactionData.class
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
public List<TransactionData> getUnconfirmedTransactions() {
|
public List<TransactionData> getUnconfirmedTransactions() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
return repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
return repository.getTransactionRepository().getAllUnconfirmedTransactions();
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,35 +194,25 @@ public class TransactionsResource {
|
|||||||
implementation = TransactionData.class
|
implementation = TransactionData.class
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
extensions = {
|
|
||||||
@Extension(
|
|
||||||
name = "translation",
|
|
||||||
properties = {
|
|
||||||
@ExtensionProperty(
|
|
||||||
name = "description.key",
|
|
||||||
value = "success_response:description"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public List<TransactionData> searchTransactions(@QueryParam("startBlock") Integer startBlock, @QueryParam("blockLimit") Integer blockLimit,
|
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
|
||||||
|
public List<TransactionData> searchTransactions(@QueryParam("startBlock") Integer startBlock, @QueryParam("blockLimit") Integer blockLimit,
|
||||||
@QueryParam("txType") Integer txTypeNum, @QueryParam("address") String address, @Parameter(
|
@QueryParam("txType") Integer txTypeNum, @QueryParam("address") String address, @Parameter(
|
||||||
ref = "limit"
|
ref = "limit"
|
||||||
) @QueryParam("limit") int limit, @Parameter(
|
) @QueryParam("limit") int limit, @Parameter(
|
||||||
ref = "offset"
|
ref = "offset"
|
||||||
) @QueryParam("offset") int offset) {
|
) @QueryParam("offset") int offset) {
|
||||||
if ((txTypeNum == null || txTypeNum == 0) && (address == null || address.isEmpty()))
|
if ((txTypeNum == null || txTypeNum == 0) && (address == null || address.isEmpty()))
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
|
|
||||||
TransactionType txType = null;
|
TransactionType txType = null;
|
||||||
if (txTypeNum != null) {
|
if (txTypeNum != null) {
|
||||||
txType = TransactionType.valueOf(txTypeNum);
|
txType = TransactionType.valueOf(txTypeNum);
|
||||||
if (txType == null)
|
if (txType == null)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_CRITERIA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -319,7 +232,7 @@ public class TransactionsResource {
|
|||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,6 +261,7 @@ public class TransactionsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.TRANSFORMATION_ERROR})
|
||||||
public String signTransaction(SimpleTransactionSignRequest signRequest) {
|
public String signTransaction(SimpleTransactionSignRequest signRequest) {
|
||||||
try {
|
try {
|
||||||
// Append null signature on the end before transformation
|
// Append null signature on the end before transformation
|
||||||
@ -364,7 +278,7 @@ public class TransactionsResource {
|
|||||||
|
|
||||||
return Base58.encode(signedBytes);
|
return Base58.encode(signedBytes);
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.UNKNOWN, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,6 +309,7 @@ public class TransactionsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.INVALID_DATA, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public String processTransaction(String rawBytes58) {
|
public String processTransaction(String rawBytes58) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
byte[] rawBytes = Base58.decode(rawBytes58);
|
byte[] rawBytes = Base58.decode(rawBytes58);
|
||||||
@ -402,11 +317,11 @@ public class TransactionsResource {
|
|||||||
|
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
if (!transaction.isSignatureValid())
|
if (!transaction.isSignatureValid())
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE);
|
||||||
|
|
||||||
ValidationResult result = transaction.isValid();
|
ValidationResult result = transaction.isValid();
|
||||||
if (result != ValidationResult.OK)
|
if (result != ValidationResult.OK)
|
||||||
throw new ApiException(400, ApiError.INVALID_DATA.getCode(), "Transaction invalid: " + result.name());
|
throw createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
repository.getTransactionRepository().save(transactionData);
|
repository.getTransactionRepository().save(transactionData);
|
||||||
repository.getTransactionRepository().unconfirmTransaction(transactionData);
|
repository.getTransactionRepository().unconfirmTransaction(transactionData);
|
||||||
@ -414,11 +329,11 @@ public class TransactionsResource {
|
|||||||
|
|
||||||
return "true";
|
return "true";
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.UNKNOWN, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -448,6 +363,7 @@ public class TransactionsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_SIGNATURE, ApiError.INVALID_DATA, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
public TransactionData decodeTransaction(String rawBytes58) {
|
public TransactionData decodeTransaction(String rawBytes58) {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
byte[] rawBytes = Base58.decode(rawBytes58);
|
byte[] rawBytes = Base58.decode(rawBytes58);
|
||||||
@ -465,23 +381,28 @@ public class TransactionsResource {
|
|||||||
|
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
if (hasSignature && !transaction.isSignatureValid())
|
if (hasSignature && !transaction.isSignatureValid())
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_SIGNATURE);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_SIGNATURE);
|
||||||
|
|
||||||
ValidationResult result = transaction.isValid();
|
ValidationResult result = transaction.isValid();
|
||||||
if (result != ValidationResult.OK)
|
if (result != ValidationResult.OK)
|
||||||
throw new ApiException(400, ApiError.INVALID_DATA.getCode(), "Transaction invalid: " + result.name());
|
throw createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
if (!hasSignature)
|
if (!hasSignature)
|
||||||
transactionData.setSignature(null);
|
transactionData.setSignature(null);
|
||||||
|
|
||||||
return transactionData;
|
return transactionData;
|
||||||
} catch (TransformationException e) {
|
} catch (TransformationException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.UNKNOWN, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||||
} catch (ApiException e) {
|
} catch (ApiException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.REPOSITORY_ISSUE, e);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ApiException createTransactionInvalidException(HttpServletRequest request, ValidationResult result) {
|
||||||
|
String translatedResult = Translator.INSTANCE.translate("TransactionValidity", request.getLocale().getLanguage(), result.name());
|
||||||
|
return ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSACTION_INVALID, null, translatedResult);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package api;
|
package api.resource;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
@ -31,6 +31,10 @@ import com.google.common.hash.HashCode;
|
|||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
import com.google.common.primitives.Longs;
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
|
import api.ApiError;
|
||||||
|
import api.ApiErrors;
|
||||||
|
import api.ApiExceptionFactory;
|
||||||
|
|
||||||
@Path("/utils")
|
@Path("/utils")
|
||||||
@Produces({
|
@Produces({
|
||||||
MediaType.TEXT_PLAIN
|
MediaType.TEXT_PLAIN
|
||||||
@ -67,11 +71,12 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
public String fromBase64(String base64) {
|
public String fromBase64(String base64) {
|
||||||
try {
|
try {
|
||||||
return HashCode.fromBytes(Base64.getDecoder().decode(base64.trim())).toString();
|
return HashCode.fromBytes(Base64.getDecoder().decode(base64.trim())).toString();
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_DATA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,11 +104,12 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
public String base64from58(String base58) {
|
public String base64from58(String base58) {
|
||||||
try {
|
try {
|
||||||
return HashCode.fromBytes(Base58.decode(base58.trim())).toString();
|
return HashCode.fromBytes(Base58.decode(base58.trim())).toString();
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_DATA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,6 +195,7 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
public String getMnemonic(@QueryParam("entropy") String suppliedEntropy) {
|
public String getMnemonic(@QueryParam("entropy") String suppliedEntropy) {
|
||||||
/*
|
/*
|
||||||
* BIP39 word lists have 2048 entries so can be represented by 11 bits.
|
* BIP39 word lists have 2048 entries so can be represented by 11 bits.
|
||||||
@ -201,12 +208,12 @@ public class UtilsResource {
|
|||||||
try {
|
try {
|
||||||
entropy = Base58.decode(suppliedEntropy);
|
entropy = Base58.decode(suppliedEntropy);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_DATA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be 16-bytes
|
// Must be 16-bytes
|
||||||
if (entropy.length != 16)
|
if (entropy.length != 16)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_DATA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
} else {
|
} else {
|
||||||
// Generate entropy internally
|
// Generate entropy internally
|
||||||
UUID uuid = UUID.randomUUID();
|
UUID uuid = UUID.randomUUID();
|
||||||
@ -296,16 +303,17 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
public String privateKey(@PathParam("entropy") String entropy58) {
|
public String privateKey(@PathParam("entropy") String entropy58) {
|
||||||
byte[] entropy;
|
byte[] entropy;
|
||||||
try {
|
try {
|
||||||
entropy = Base58.decode(entropy58);
|
entropy = Base58.decode(entropy58);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_DATA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entropy.length != 16)
|
if (entropy.length != 16)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_DATA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
|
||||||
byte[] privateKey = Crypto.digest(entropy);
|
byte[] privateKey = Crypto.digest(entropy);
|
||||||
|
|
||||||
@ -328,16 +336,17 @@ public class UtilsResource {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
public String publicKey(@PathParam("privateKey") String privateKey58) {
|
public String publicKey(@PathParam("privateKey") String privateKey58) {
|
||||||
byte[] privateKey;
|
byte[] privateKey;
|
||||||
try {
|
try {
|
||||||
privateKey = Base58.decode(privateKey58);
|
privateKey = Base58.decode(privateKey58);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_DATA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (privateKey.length != 32)
|
if (privateKey.length != 32)
|
||||||
throw ApiErrorFactory.getInstance().createError(ApiError.INVALID_DATA);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
|
||||||
byte[] publicKey = new PrivateKeyAccount(null, privateKey).getPublicKey();
|
byte[] publicKey = new PrivateKeyAccount(null, privateKey).getPublicKey();
|
||||||
|
|
@ -30,7 +30,6 @@ import org.bitcoinj.core.Sha256Hash;
|
|||||||
import org.bitcoinj.core.StoredBlock;
|
import org.bitcoinj.core.StoredBlock;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.bitcoinj.core.Transaction;
|
||||||
import org.bitcoinj.core.TransactionOutput;
|
import org.bitcoinj.core.TransactionOutput;
|
||||||
import org.bitcoinj.core.Utils;
|
|
||||||
import org.bitcoinj.core.VerificationException;
|
import org.bitcoinj.core.VerificationException;
|
||||||
import org.bitcoinj.core.listeners.NewBestBlockListener;
|
import org.bitcoinj.core.listeners.NewBestBlockListener;
|
||||||
import org.bitcoinj.net.discovery.DnsDiscovery;
|
import org.bitcoinj.net.discovery.DnsDiscovery;
|
||||||
@ -109,6 +108,7 @@ public class BTC {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public void saveAsBinary(File file) throws IOException {
|
public void saveAsBinary(File file) throws IOException {
|
||||||
try (final FileOutputStream fileOutputStream = new FileOutputStream(file, false)) {
|
try (final FileOutputStream fileOutputStream = new FileOutputStream(file, false)) {
|
||||||
MessageDigest digest = Sha256Hash.newDigest();
|
MessageDigest digest = Sha256Hash.newDigest();
|
52
src/main/java/globalization/BIP39WordList.java
Normal file
52
src/main/java/globalization/BIP39WordList.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package globalization;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
/** Providing multi-language BIP39 word lists, downloaded from https://github.com/bitcoin/bips/tree/master/bip-0039 */
|
||||||
|
public enum BIP39WordList {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
private Logger LOGGER = LogManager.getLogger(BIP39WordList.class);
|
||||||
|
|
||||||
|
private Map<String, List<String>> wordListsByLang;
|
||||||
|
|
||||||
|
private BIP39WordList() {
|
||||||
|
wordListsByLang = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized List<String> getByLang(String lang) {
|
||||||
|
List<String> wordList = wordListsByLang.get(lang);
|
||||||
|
|
||||||
|
if (wordList == null) {
|
||||||
|
ClassLoader loader = this.getClass().getClassLoader();
|
||||||
|
|
||||||
|
try (InputStream inputStream = loader.getResourceAsStream("BIP39/wordlist_" + lang + ".txt")) {
|
||||||
|
if (inputStream == null) {
|
||||||
|
LOGGER.warn("Can't locate '" + lang + "' BIP39 wordlist");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
wordList = new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.toList());
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.warn("Error reading '" + lang + "' BIP39 wordlist", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
wordListsByLang.put(lang, wordList);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(wordList);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
52
src/main/java/globalization/Translator.java
Normal file
52
src/main/java/globalization/Translator.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package globalization;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.MissingResourceException;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
public enum Translator {
|
||||||
|
INSTANCE;
|
||||||
|
|
||||||
|
private final Logger LOGGER = LogManager.getLogger(Translator.class);
|
||||||
|
private final String DEFAULT_LANG = Locale.getDefault().getLanguage();
|
||||||
|
|
||||||
|
private final Map<String, ResourceBundle> resourceBundles = new HashMap<>();
|
||||||
|
|
||||||
|
private synchronized ResourceBundle getOrLoadResourceBundle(String className, String lang) {
|
||||||
|
final String bundleKey = className + ":" + lang;
|
||||||
|
|
||||||
|
ResourceBundle resourceBundle = resourceBundles.get(bundleKey);
|
||||||
|
if (resourceBundle != null)
|
||||||
|
return resourceBundle;
|
||||||
|
|
||||||
|
try {
|
||||||
|
resourceBundle = ResourceBundle.getBundle("i18n." + className, Locale.forLanguageTag(lang));
|
||||||
|
} catch (MissingResourceException e) {
|
||||||
|
LOGGER.warn("Can't locate '" + lang + "' translation resource bundle for " + className, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceBundles.put(bundleKey, resourceBundle);
|
||||||
|
|
||||||
|
return resourceBundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String translate(final String className, final String key) {
|
||||||
|
return this.translate(className, DEFAULT_LANG, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String translate(final String className, final String lang, final String key, final Object... args) {
|
||||||
|
ResourceBundle resourceBundle = getOrLoadResourceBundle(className, lang);
|
||||||
|
|
||||||
|
if (resourceBundle == null || !resourceBundle.containsKey(key))
|
||||||
|
return "!!" + lang + ":" + className + "." + key + "!!";
|
||||||
|
|
||||||
|
return String.format(resourceBundle.getString(key), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user