Compare commits

..

2 Commits

Author SHA1 Message Date
8217f0c027 juste un pr de test 2024-11-13 20:02:22 +01:00
c06658d9bc [FEAT] add workflow 2024-11-13 19:56:11 +01:00
21 changed files with 90 additions and 478 deletions

3
.github/auto-assign.yml vendored Normal file
View File

@ -0,0 +1,3 @@
---
addAssignees: author
runOnDraft: true

View File

@ -1,12 +1,9 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
---
version: 2
updates:
- package-ecosystem: "" # See documentation for possible values
directory: "/" # Location of package manifests
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
interval: "daily"
commit-message:
prefix: "[DEV-OPS] (dependabot) "

14
.github/pr-title-checker-config.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"CHECKS": {
"regexp": "\\[(API,)?(API|DEV-OPS|DOC|FEAT|FIX|FIX\\-CI|STYLE)\\]( \\([A-Za-z0-9.\\-/_]+\\))? [A-Za-z0-9 ,.'\\-!/_]+$"
},
"LABEL": {
"color": "EEEEEE",
"name": "title needs formatting"
},
"MESSAGES": {
"failure": "Failing CI test",
"notice": "Title of the PR MUST respect format: [{TYPE}] clear description without typos in english\n with {TYPE}:\n * [API] Change API that permit to access on the application (un-compatibility only). This one can specifically added with [API,{TYPE}]\n * [DEV-OPS] Update automatic build system, method to deliver application/packages, ...\n * [DOC] Update or add some documentation.\n * [FEAT] Develop a new feature\n * [FIX] When fixing issue\n * [FIX-CI] When the CI fail to build and we apply a correction to set it work again.\n * [STYLE] Update of the style tools/checker, or add/remove rules.\n Examples:\n [FEAT] My beautiful feature\n [API,FIX] Change API to fix typo\n [FIX] (module) Correct part of ...",
"success": "All OK"
}
}

View File

@ -1,16 +0,0 @@
---
name: "Assign PR Author as Assignee"
on:
pull_request:
types:
- opened
jobs:
assign-pr-author-as-assignee:
runs-on: ubuntu-latest
steps:
- name: "Assign Author as Assignee"
uses: itsOliverBott/assign-pr-author-as-assignee@latest
with:
token: ${{ secrets.GITHUB_TOKEN }}

13
.github/workflows/auto-assign.yml vendored Normal file
View File

@ -0,0 +1,13 @@
---
name: 'Auto Assign'
"on":
pull_request:
types: [opened]
jobs:
add-assignee:
runs-on: ubuntu-latest
steps:
- uses: kentaro-m/auto-assign-action@v2.0.0
with:
configuration-path: '.github/auto-assign.yml'

View File

@ -1,33 +0,0 @@
---
name: "Check PR title"
on:
pull_request:
types:
- opened
- edited
- synchronize
- ready_for_review
- reopened
jobs:
check-title:
runs-on: ubuntu-latest
steps:
- name: "Check title"
uses: Slashgear/action-check-pr-title@v4.3.0
with:
regexp: "\\[(API,)?(API|DEV-OPS|DOC|FEAT|FIX|FIX\\-CI|STYLE)\\]( \\([A-Za-z0-9.\\-]+\\))? [A-Za-z0-9 ,.'\\-!]+$"
helpMessage: |
Title of the PR MUST respect format: "[{TYPE}] clear description without typos in english" with {TYPE}:
* [API] Change API that permit to access on the application (un-compatibility only). This one can specifically added with [API,{TYPE}]
* [DEV-OPS] Update automatic build system, method to deliver application/packages, ...
* [DOC] Update or add some documentation.
* [FEAT] Develop a new feature
* [FIX] When fixing issue
* [FIX-CI] When the CI fail to build and we apply a correction to set it work again.
* [STYLE] Update of the style tools/checker, or add/remove rules.
Examples:
[FEAT] My beautiful feature
[API,FIX] Change API to fix typo
[FIX] (module) Correct part of ...

View File

@ -18,9 +18,9 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
@ -32,4 +32,4 @@ jobs:
# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
uses: advanced-security/maven-dependency-submission-action@4f64ddab9d742a4806eeb588d238e4c311a8397d
uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6

22
.github/workflows/pr-title-checker.yml vendored Normal file
View File

@ -0,0 +1,22 @@
---
name: "PR Title Checker"
on:
pull_request:
types:
- opened
- edited
- synchronize
- ready_for_review
- reopened
- labeled
- unlabeled
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: thehanimo/pr-title-checker@v1.4.0
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
pass_on_octokit_error: false

137
README.md
View File

@ -1,137 +0,0 @@
Archi-data
==========
Archi-data is a framework that simplify:
- Creating a REST server with:
- Right control
- Swagger display interface
- Normalize error generate by the server
- Access to the DB:
- introspect Object and insert in the TD (SQLITE & MY-SQL)
- Manage migration
- JPA checker for many generic request
- simplify the request of the Test-service
Develop in cmd-line:
--------------------
The first step is configuring your JAVA version (or select the JVM with the OS)
```bash
export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH
```
Install the dependency:
```bash
mvn install
```
Run the test
```bash
mvn test
```
Install it for external use
```bash
mvn install
```
Develop With Eclipse:
--------------------
Import the project:
- Open a (new) project on eclipse
- `File` -> `Import`
- `Maven` -> `Existing Maven project`
- Select the `pom.xml` file and click on import
Run the Test:
- Open the package `test.kar.archidata`
- Click right on it
- Select `Debug As` -> `JUnit Test`
Install in the local maven repository:
- Click right on the `pom.xml` file
- Select `Run As` -> `Maven install`
Somes tools:
============
Auto-update dependency:
-----------------------
Auto-update to the last version dependency:
```bash
mvn versions:use-latest-versions
```
Format the code
---------------
Simply run the cmd-line:
```bash
mvn formatter:format
```
Reformat XML file like the pom.xml
```bash
XMLLINT_INDENT=" " xmllint --format "back/pom.xml" -o "back/pom.xml"
```
Enable the pre-commit checker
-----------------------------
```bash
./tools/configure_precommit.bash
```
> **_Note_**: You can change the code in `.git/hooks/pre-commit` by replacing `formatter:verify` with `formatter:format` to auto format the code @ every commit
Run Spot-bug:
------------
```bash
mvn spotbugs:check
```
Add Gitea in the dependency for the registry:
=============================================
Read instruction for tocken in ~/.m2/setting.xml
edit file: ```~/.m2/settings.xml```
```xml
<settings>
<servers>
<server>
<id>gitea</id>
<configuration>
<httpHeaders>
<property>
<name>Authorization</name>
<value>token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</value>
</property>
</httpHeaders>
</configuration>
</server>
</servers>
</settings>
```
release:
========
```bash
export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH
mvn install
mvn deploy
```

View File

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>kangaroo-and-rabbit</groupId>
<artifactId>archidata</artifactId>
<version>0.19.0</version>
<version>0.17.1-SNAPSHOT</version>
<properties>
<java.version>21</java.version>
<maven.compiler.version>3.1</maven.compiler.version>

View File

@ -21,7 +21,6 @@ import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
@ -236,14 +235,6 @@ public class AnnotationTools {
return ((Pattern) annotation[0]).regexp();
}
public static boolean getConstraintsEmail(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Email.class);
if (annotation.length == 0) {
return false;
}
return true;
}
public static boolean isAnnotationGroup(final Field field, final Class<?> annotationType) {
try {
final Annotation[] anns = field.getAnnotations();

View File

@ -1246,10 +1246,6 @@ public class DataAccess {
public static void addElement(final PreparedStatement ps, final Object value, final CountInOut iii)
throws Exception {
if (value == null) {
ps.setNull(iii.value, Types.INTEGER);
return;
}
if (value instanceof final UUID tmp) {
final byte[] dataByte = UuidUtils.asBytes(tmp);
ps.setBytes(iii.value, dataByte);

View File

@ -30,9 +30,7 @@ import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import jakarta.validation.constraints.NotNull;
@ -154,10 +152,7 @@ public class AddOnDataJson implements DataAccessAddOn {
}
LOGGER.warn("Maybe fail to translate Model in datajson list: List<{}>", listClass.getCanonicalName());
}
final TypeFactory typeFactory = objectMapper.getTypeFactory();
final JavaType fieldType = typeFactory.constructType(field.getGenericType());
final Object dataParsed = objectMapper.readValue(jsonData, fieldType);
//final Object dataParsed = objectMapper.readValue(jsonData, field.getType());
final Object dataParsed = objectMapper.readValue(jsonData, field.getType());
field.set(data, dataParsed);
}
}

View File

@ -401,27 +401,6 @@ public class CheckJPA<T> implements CheckFunctionInterface {
}
});
}
if (AnnotationTools.getConstraintsEmail(field)) {
final String emailPattern = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
final Pattern pattern = Pattern.compile(emailPattern);
add(fieldName,
(
final String baseName,
final T data,
final List<String> modifiedValue,
final QueryOptions options) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final String elemTyped = (String) elem;
if (!pattern.matcher(elemTyped).find()) {
throw new InputException(baseName + fieldName,
"does not match the required pattern[email] (constraints) must be '"
+ emailPattern + "'");
}
});
}
} else if (type == JsonValue.class) {
final DataJson jsonAnnotation = AnnotationTools.getDataJson(field);
if (jsonAnnotation != null && jsonAnnotation.checker() != CheckFunctionVoid.class) {

View File

@ -12,7 +12,6 @@ import java.util.Map.Entry;
import org.kar.archidata.annotation.security.PermitTokenInURI;
import org.kar.archidata.catcher.RestErrorResponse;
import org.kar.archidata.exception.SystemException;
import org.kar.archidata.model.UserByToken;
import org.kar.archidata.tools.JWTWrapper;
import org.slf4j.Logger;
@ -24,7 +23,6 @@ import jakarta.annotation.Priority;
import jakarta.annotation.security.DenyAll;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
@ -44,40 +42,18 @@ public class AuthenticationFilter implements ContainerRequestFilter {
@Context
private ResourceInfo resourceInfo;
protected final String applicationName;
protected final String issuer;
public static final String AUTHENTICATION_SCHEME = "Bearer";
public static final String APIKEY = "ApiKey";
public AuthenticationFilter(final String applicationName) {
this.applicationName = applicationName;
this.issuer = "KarAuth";
}
public AuthenticationFilter(final String applicationName, final String issuer) {
this.applicationName = applicationName;
this.issuer = issuer;
}
public String getRequestedPath(final ContainerRequestContext requestContext) {
final Class<?> resourceClass = this.resourceInfo.getResourceClass();
final Method resourceMethod = this.resourceInfo.getResourceMethod();
final String classPath = resourceClass.isAnnotationPresent(Path.class)
? resourceClass.getAnnotation(Path.class).value()
: "";
final String methodPath = resourceMethod.isAnnotationPresent(Path.class)
? resourceMethod.getAnnotation(Path.class).value()
: "";
final String fullPath = (classPath.startsWith("/") ? "" : "/") + classPath
+ (methodPath.startsWith("/") ? "" : "/") + methodPath;
return fullPath;
}
@Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
/* logger.debug("-----------------------------------------------------"); logger.debug("---- Check if have authorization ----");
* logger.debug("-----------------------------------------------------"); logger.debug(" for:{}", requestContext.getUriInfo().getPath()); */
final Method method = this.resourceInfo.getResourceMethod();
// Access denied for all
if (method.isAnnotationPresent(DenyAll.class)) {
@ -164,13 +140,12 @@ public class AuthenticationFilter implements ContainerRequestFilter {
final List<String> roles = Arrays.asList(rolesAnnotation.value());
// check if the user have the right:
boolean haveRight = false;
try {
haveRight = checkRight(requestContext, userContext, roles);
} catch (final SystemException e) {
// TODO Auto-generated catch block
e.printStackTrace();
for (final String role : roles) {
if (userContext.isUserInRole(role)) {
haveRight = true;
break;
}
}
// Is user valid?
if (!haveRight) {
LOGGER.error("REJECTED not enought right : {} require: {}", requestContext.getUriInfo().getPath(), roles);
@ -182,18 +157,6 @@ public class AuthenticationFilter implements ContainerRequestFilter {
// logger.debug("Get local user : {} / {}", user, userByToken);
}
protected boolean checkRight(
final ContainerRequestContext requestContext,
final MySecurityContext userContext,
final List<String> roles) throws SystemException {
for (final String role : roles) {
if (userContext.isUserInRole(this.applicationName + "/" + role)) {
return true;
}
}
return false;
}
private boolean isTokenBasedAuthentication(final String authorizationHeader) {
// Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace
@ -230,7 +193,7 @@ public class AuthenticationFilter implements ContainerRequestFilter {
// must be override to be good implementation
protected UserByToken validateJwtToken(final String authorization) throws Exception {
// logger.debug(" validate token : " + authorization);
final JWTClaimsSet ret = JWTWrapper.validateToken(authorization, this.issuer, null);
final JWTClaimsSet ret = JWTWrapper.validateToken(authorization, "KarAuth", null);
// check the token is valid !!! (signed and coherent issuer...
if (ret == null) {
LOGGER.error("The token is not valid: '{}'", authorization);
@ -245,16 +208,13 @@ public class AuthenticationFilter implements ContainerRequestFilter {
user.type = UserByToken.TYPE_USER;
final Object rowRight = ret.getClaim("right");
if (rowRight != null) {
LOGGER.info("Detect right in Authentication Filter: {}", rowRight);
user.right = (Map<String, Map<String, Object>>) ret.getClaim("right");
/*
final Map<String, Map<String, Object>> rights = (Map<String, Map<String, Object>>) ret.getClaim("right");
if (rights.containsKey(this.applicationName)) {
user.right = rights.get(this.applicationName);
} else {
LOGGER.error("Connect with no right for this application='{}' full Right='{}'", this.applicationName,
rights);
}
*/
}
// logger.debug("request user: '{}' right: '{}' row='{}'", userUID, user.right, rowRight);
return user;

View File

@ -1,17 +1,13 @@
package org.kar.archidata.filter;
import java.security.Principal;
import java.util.Set;
import org.kar.archidata.model.UserByToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.ws.rs.core.SecurityContext;
// https://simplapi.wordpress.com/2015/09/19/jersey-jax-rs-securitycontext-in-action/
public class MySecurityContext implements SecurityContext {
private static final Logger LOGGER = LoggerFactory.getLogger(MySecurityContext.class);
class MySecurityContext implements SecurityContext {
private final GenericContext contextPrincipale;
private final String sheme;
@ -26,126 +22,15 @@ public class MySecurityContext implements SecurityContext {
return this.contextPrincipale;
}
public Object getRightOfRoleInGroup(final String group, final String role) {
if (this.contextPrincipale.userByToken != null) {
return this.contextPrincipale.userByToken.getRight(group, role);
}
return null;
}
public Set<String> getGroups() {
if (this.contextPrincipale.userByToken != null) {
return this.contextPrincipale.userByToken.getGroups();
}
return Set.of();
}
public boolean groupExist(final String group) {
if (this.contextPrincipale.userByToken != null) {
return this.contextPrincipale.userByToken.groupExist(group);
}
return false;
}
// Not sure the Long type is definitive.
public Long getUserID() {
if (this.contextPrincipale.userByToken != null) {
return this.contextPrincipale.userByToken.id;
}
return null;
}
public boolean checkRightInGroup(
final String group,
final String role,
final boolean needRead,
final boolean needWrite) {
if ("USER".equals(role)) {
if (groupExist(group)) {
return true;
}
return false;
}
// get associated Roles:
final Object rightPart = getRightOfRoleInGroup(group, role);
LOGGER.info("detect : {}", rightPart);
long dataRight = 0;
if (rightPart instanceof final Long rightPartCasted) {
dataRight = rightPartCasted;
} else if (rightPart instanceof final Integer rightPartCasted) {
dataRight = rightPartCasted;
}
if (dataRight == PartRight.READ_WRITE.getValue()) {
return true;
}
if (!needRead && needWrite && dataRight == PartRight.WRITE.getValue()) {
return true;
}
if (needRead && !needWrite && dataRight == PartRight.READ.getValue()) {
return true;
}
return false;
}
@Override
public boolean isUserInRole(final String role) {
String roleEdit = role;
boolean needRead = false;
boolean needWrite = false;
// Check if the API overwrite the right needed for this API.
if (roleEdit.contains(":")) {
if (roleEdit.endsWith(":w")) {
try {
roleEdit = roleEdit.substring(0, roleEdit.length() - 2);
} catch (final IndexOutOfBoundsException ex) {
LOGGER.error("Fail to extract role of '{}'", role);
ex.printStackTrace();
return false;
}
needWrite = true;
} else if (roleEdit.endsWith(":r")) {
try {
roleEdit = roleEdit.substring(0, roleEdit.length() - 2);
} catch (final IndexOutOfBoundsException ex) {
LOGGER.error("Fail to extract role of '{}'", role);
ex.printStackTrace();
return false;
}
needRead = true;
} else if (roleEdit.endsWith(":rw")) {
try {
roleEdit = roleEdit.substring(0, roleEdit.length() - 3);
} catch (final IndexOutOfBoundsException ex) {
LOGGER.error("Fail to extract role of '{}'", role);
ex.printStackTrace();
return false;
}
needRead = true;
needWrite = true;
} else {
LOGGER.error("Request check right of an unknow right mode: {} (after ':')", roleEdit);
return false;
if (this.contextPrincipale.userByToken != null) {
final Object value = this.contextPrincipale.userByToken.right.get(role);
if (value instanceof final Boolean ret) {
return ret;
}
}
if (roleEdit.contains("/")) {
final String[] elements = roleEdit.split("/");
return checkRightInGroup(elements[0], elements[1], needRead, needWrite);
}
// Special case, if the token is valid, it is an USER ...
if ("USER".equals(roleEdit)) {
return true;
}
return checkRightInGroup("?system?", roleEdit, needRead, needWrite);
}
public Object getRole(final String role) {
LOGGER.info("contextPrincipale={}", this.contextPrincipale);
if (this.contextPrincipale.userByToken != null) {
LOGGER.info("contextPrincipale.userByToken={}", this.contextPrincipale.userByToken);
LOGGER.info("contextPrincipale.userByToken.right={}", this.contextPrincipale.userByToken.right);
return this.contextPrincipale.userByToken.right.get(role);
}
return null;
return false;
}
@Override

View File

@ -1,30 +0,0 @@
package org.kar.archidata.filter;
import com.fasterxml.jackson.annotation.JsonValue;
public enum PartRight {
NONE(0), //
READ(1), //
WRITE(2), //
READ_WRITE(3);
private final int value;
PartRight(final int value) {
this.value = value;
}
@JsonValue
public int getValue() {
return this.value;
}
public static PartRight fromValue(final int value) {
for (final PartRight element : values()) {
if (element.getValue() == value) {
return element;
}
}
throw new IllegalArgumentException("PartRight: Unknown value: " + value);
}
}

View File

@ -28,29 +28,26 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import jakarta.ws.rs.DefaultValue;
@Table(name = "user")
@DataIfNotExists
@JsonInclude(JsonInclude.Include.NON_NULL)
public class User extends GenericDataSoftDelete {
@NotNull
@Column(length = 128)
@Size(min = 3, max = 128)
@Pattern(regexp = "^[a-zA-Z0-9-_ \\.]+$")
public String login = null;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
public Timestamp lastConnection = null;
@DefaultValue("'0'")
@Column(nullable = false)
public boolean admin = false;
@DefaultValue("'0'")
@Column(nullable = false)
public boolean blocked = false;
@Column(length = 512)
public String blockedReason;
@DefaultValue("'0'")
@Column(nullable = false)
public boolean removed = false;
@Schema(description = "List of Id of the specific covers")
@DataJson(targetEntity = Data.class)
@ -59,8 +56,7 @@ public class User extends GenericDataSoftDelete {
@Override
public String toString() {
return "User [login=" + this.login + ", last=" + this.lastConnection + ", blocked=" + this.blocked
+ ", blockedReason=" + this.blockedReason + "]";
return "User [login=" + this.login + ", last=" + this.lastConnection + ", admin=" + this.admin + "]";
}
}

View File

@ -2,7 +2,6 @@ package org.kar.archidata.model;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class UserByToken {
public static final int TYPE_USER = -1;
@ -14,35 +13,13 @@ public class UserByToken {
public Long parentId = null; // FOr application, this is the id of the application, and of user token, this is the USERID
public String name = null;
// Right map
public Map<String, Map<String, Object>> right = new HashMap<>();
public Map<String, Object> right = new HashMap<>();
public Set<String> getGroups() {
return this.right.keySet();
}
public boolean groupExist(final String group) {
if (!this.right.containsKey(group)) {
return false;
}
return this.right.containsKey(group);
}
public Object getRight(final String group, final String key) {
if (!this.right.containsKey(group)) {
return null;
}
final Map<String, Object> rightGroup = this.right.get(group);
if (!rightGroup.containsKey(key)) {
return null;
}
return rightGroup.get(key);
}
public boolean hasRight(final String group, final String key, final Object value) {
final Object data = getRight(group, key);
if (data == null) {
public boolean hasRight(final String key, final Object value) {
if (!this.right.containsKey(key)) {
return false;
}
final Object data = this.right.get(key);
if (data instanceof final Boolean elem) {
if (value instanceof final Boolean castVal) {
if (elem.equals(castVal)) {

View File

@ -121,7 +121,7 @@ public class RESTApi {
}
@SuppressWarnings("unchecked")
public <T, U> T modelSendJson(final String model, final Class<T> clazz, final String urlOffset, String body)
protected <T, U> T modelSendJson(final String model, final Class<T> clazz, final String urlOffset, String body)
throws RESTErrorResponseExeption, IOException, InterruptedException {
final HttpClient client = HttpClient.newHttpClient();
// client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
@ -166,7 +166,7 @@ public class RESTApi {
}
@SuppressWarnings("unchecked")
public <T> T modelSendMap(
protected <T> T modelSendMap(
final String model,
final Class<T> clazz,
final String urlOffset,

View File

@ -1 +1 @@
0.19.0
0.17.1-dev