code format

This commit is contained in:
Alfredo Oliviero 2024-04-17 20:59:23 +02:00
parent ae67b28d16
commit 866c9b2d9a
16 changed files with 175 additions and 181 deletions

View File

@ -15,11 +15,11 @@
<param-value>org.gcube.service.idm.rest</param-value> <param-value>org.gcube.service.idm.rest</param-value>
</init-param> </init-param>
<init-param> <init-param>
<param-name>jersey.config.server.provider.packages</param-name> <param-name>jersey.config.server.provider.packages</param-name>
<param-value> <param-value>
org.gcube.service.idm.mappers org.gcube.service.idm.mappers
</param-value> </param-value>
</init-param> </init-param>
</servlet> </servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>idm</servlet-name> <servlet-name>idm</servlet-name>

View File

@ -220,7 +220,7 @@ solution: bind version, or exclude them in usermanagement-core
<version>3.1.0</version> <version>3.1.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> --> </dependency> -->
<!-- END Required for Enunciate plugin --> <!-- END Required for Enunciate plugin -->
<!-- Test libraries --> <!-- Test libraries -->
@ -281,7 +281,7 @@ java.lang.NoClassDefFoundError: org/apache/http/ssl/TrustStrategy
</exclusions> </exclusions>
</dependency> </dependency>
<!-- <!--
<dependency> <dependency>
<groupId>org.gcube.resources.discovery</groupId> <groupId>org.gcube.resources.discovery</groupId>
<artifactId>ic-client</artifactId> <artifactId>ic-client</artifactId>

View File

@ -24,7 +24,8 @@ import jakarta.ws.rs.WebApplicationException;
public class AdminKeycloakController { public class AdminKeycloakController {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(AdminKeycloakController.class); private static final org.slf4j.Logger logger = LoggerFactory.getLogger(AdminKeycloakController.class);
// TODO: Using Keycloak Admin Client to create user with roles (Realm and Client level) // TODO: Using Keycloak Admin Client to create user with roles (Realm and Client
// level)
// https://gist.github.com/thomasdarimont/c4e739c5a319cf78a4cff3b87173a84b // https://gist.github.com/thomasdarimont/c4e739c5a319cf78a4cff3b87173a84b
public static UserRepresentation createUser(String username, String email, String password, String firstName, public static UserRepresentation createUser(String username, String email, String password, String firstName,
String lastName, Map<String, List<String>> attributes, List<String> roles) throws WebApplicationException { String lastName, Map<String, List<String>> attributes, List<String> roles) throws WebApplicationException {
@ -188,7 +189,7 @@ public class AdminKeycloakController {
public static ClientRepresentation createClient(ClientRepresentation newClient) { public static ClientRepresentation createClient(ClientRepresentation newClient) {
RealmResource realmResource = KkClientFactory.getSingleton().getKKRealm(); RealmResource realmResource = KkClientFactory.getSingleton().getKKRealm();
ClientsResource clientsResource = realmResource.clients(); ClientsResource clientsResource = realmResource.clients();
String newClientId = null; String newClientId = null;
// throws exception if creationResponse is failed // throws exception if creationResponse is failed

View File

@ -8,8 +8,6 @@ import org.gcube.common.security.Owner;
import org.gcube.common.security.providers.SecretManagerProvider; import org.gcube.common.security.providers.SecretManagerProvider;
import org.gcube.common.security.secrets.Secret; import org.gcube.common.security.secrets.Secret;
public class AuthController { public class AuthController {
public final static String IDM_SERVICE_READ = "idm-service-read"; public final static String IDM_SERVICE_READ = "idm-service-read";
public final static String IDM_SERVICE_ADMIN = "idm-service-admin"; public final static String IDM_SERVICE_ADMIN = "idm-service-admin";
@ -61,9 +59,8 @@ public class AuthController {
String access_token = getAccessToken(); String access_token = getAccessToken();
Owner owner = getOwner(); Owner owner = getOwner();
for (String role : roles) {
for (String role : roles){ if (checkContextRole(role, owner) || checkRealmRole(role, access_token)) {
if ( checkContextRole(role, owner) || checkRealmRole(role, access_token)){
return true; return true;
} }
} }

View File

@ -16,31 +16,31 @@ public class KCRolesController {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(KCRolesController.class); private static final org.slf4j.Logger logger = LoggerFactory.getLogger(KCRolesController.class);
public enum REPRESENTATION { public enum REPRESENTATION {
full, compact, name, id full, compact, name, id
} }
public static Object formatListRoles(List<RoleRepresentation> roles, KCRolesController.REPRESENTATION format) { public static Object formatListRoles(List<RoleRepresentation> roles, KCRolesController.REPRESENTATION format) {
if (format.equals(KCRolesController.REPRESENTATION.id)) {
List<String> ids = new ArrayList<String>();
if (roles != null) {
for (RoleRepresentation role : roles) {
ids.add(role.getId());
}
}
return ids;
} else if (format.equals(KCRolesController.REPRESENTATION.name)) {
List<String> names = new ArrayList<String>();
if (roles != null) {
for (RoleRepresentation role : roles) {
names.add(role.getName());
}
}
return names;
} else
return roles;
}
if (format.equals(KCRolesController.REPRESENTATION.id)) {
List<String> ids = new ArrayList<String>();
if (roles != null) {
for (RoleRepresentation role : roles) {
ids.add(role.getId());
}
}
return ids;
} else if (format.equals(KCRolesController.REPRESENTATION.name)) {
List<String> names = new ArrayList<String>();
if (roles != null) {
for (RoleRepresentation role : roles) {
names.add(role.getName());
}
}
return names;
} else
return roles;
}
public static List<RoleRepresentation> getRoles() { public static List<RoleRepresentation> getRoles() {
logger.info("Searching users for context"); logger.info("Searching users for context");
ClientResource client = KkClientFactory.getSingleton().getKKClient(); ClientResource client = KkClientFactory.getSingleton().getKKClient();

View File

@ -23,8 +23,8 @@ public class KCUserController {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(KCUserController.class); private static final org.slf4j.Logger logger = LoggerFactory.getLogger(KCUserController.class);
public enum REPRESENTATION { public enum REPRESENTATION {
full, compact, username, email, id, email_username, fullname full, compact, username, email, id, email_username, fullname
} }
public static UsersResource users() { public static UsersResource users() {
RealmResource realm = KkClientFactory.getSingleton().getKKRealm(); RealmResource realm = KkClientFactory.getSingleton().getKKRealm();
@ -32,51 +32,50 @@ public class KCUserController {
return users; return users;
} }
public static Object formatListUsers(List<UserRepresentation> users, KCUserController.REPRESENTATION format) {
public static Object formatListUsers(List<UserRepresentation> users, KCUserController.REPRESENTATION format){ if (format.equals(KCUserController.REPRESENTATION.username)) {
List<String> usernames = new ArrayList<String>();
if (users != null) {
for (UserRepresentation user : users) {
usernames.add(user.getUsername());
}
}
return usernames;
if (format.equals(KCUserController.REPRESENTATION.username)) { } else if (format.equals(KCUserController.REPRESENTATION.email)) {
List<String> usernames = new ArrayList<String>(); List<String> emails = new ArrayList<String>();
if (users != null) { if (users != null) {
for (UserRepresentation user : users) { for (UserRepresentation user : users) {
usernames.add(user.getUsername()); emails.add(user.getEmail());
} }
} }
return usernames; return emails;
} else if (format.equals(KCUserController.REPRESENTATION.id)) {
} else if (format.equals(KCUserController.REPRESENTATION.email)) { List<String> ids = new ArrayList<String>();
List<String> emails = new ArrayList<String>(); if (users != null) {
if (users != null) { for (UserRepresentation user : users) {
for (UserRepresentation user : users) { ids.add(user.getId());
emails.add(user.getEmail()); }
} }
} return ids;
return emails; } else if (format.equals(KCUserController.REPRESENTATION.email_username)) {
} else if (format.equals(KCUserController.REPRESENTATION.id)) {
List<String> ids = new ArrayList<String>();
if (users != null) {
for (UserRepresentation user : users) {
ids.add(user.getId());
}
}
return ids;
}else if (format.equals(KCUserController.REPRESENTATION.email_username)) {
Map<String, String> usernamesAndFullnames = new HashMap<String, String>(); Map<String, String> usernamesAndFullnames = new HashMap<String, String>();
users.forEach(user -> usernamesAndFullnames.put(user.getUsername(), user.getEmail())); users.forEach(user -> usernamesAndFullnames.put(user.getUsername(), user.getEmail()));
return usernamesAndFullnames; return usernamesAndFullnames;
}else if (format.equals(KCUserController.REPRESENTATION.fullname)) { } else if (format.equals(KCUserController.REPRESENTATION.fullname)) {
List<String> fullnames = new ArrayList<String>(); List<String> fullnames = new ArrayList<String>();
if (users != null) { if (users != null) {
for (UserRepresentation user : users) { for (UserRepresentation user : users) {
fullnames.add(user.getFirstName() + " " + user.getLastName()); fullnames.add(user.getFirstName() + " " + user.getLastName());
} }
} }
return fullnames; return fullnames;
} else } else
return users; return users;
} }
/** /**
* Search for users based on the given filters. * Search for users based on the given filters.
* *
@ -147,7 +146,7 @@ public class KCUserController {
RealmResource realm = KkClientFactory.getSingleton().getKKRealm(); RealmResource realm = KkClientFactory.getSingleton().getKKRealm();
UserRepresentation user = realm.users() UserRepresentation user = realm.users()
.search(username, true).stream().findFirst().orElse(null); .search(username, true).stream().findFirst().orElse(null);
if (user == null) { if (user == null) {
throw new NotFoundException("cannot retrieve user " + username); throw new NotFoundException("cannot retrieve user " + username);
} }
@ -161,12 +160,12 @@ public class KCUserController {
UserRepresentation user = realm.users() UserRepresentation user = realm.users()
.search(username, true).stream().findFirst().orElse(null); .search(username, true).stream().findFirst().orElse(null);
if (user == null) { if (user == null) {
throw new NotFoundException("cannot retrieve user " + username); throw new NotFoundException("cannot retrieve user " + username);
} }
UserResource userRes = realm.users().get(user.getId()); UserResource userRes = realm.users().get(user.getId());
if (userRes == null) { if (userRes == null) {
throw new NotFoundException("cannot retrieve user " + username); throw new NotFoundException("cannot retrieve user " + username);
} }

View File

@ -19,8 +19,7 @@ public class IsServerConfig {
private String grantType = OAuth2Constants.CLIENT_CREDENTIALS; private String grantType = OAuth2Constants.CLIENT_CREDENTIALS;
public Map<String, String> getProperties() {
public Map<String, String> getProperties(){
return this.properties; return this.properties;
} }

View File

@ -62,30 +62,32 @@ public class KkClientFactory {
logger.info("KeycloakAPICredentials object built {} - {}", config.getServerUrl(), config.getName()); logger.info("KeycloakAPICredentials object built {} - {}", config.getServerUrl(), config.getName());
} }
// public IsServerConfig configFromINI() throws NotFoundException, ServerException{ // public IsServerConfig configFromINI() throws NotFoundException,
// ServerException{
// ApplicationContext appContext = ContextProvider.get();
// SimpleCredentials credentials =
// (SimpleCredentials)appContext.authorizationProvider().getCredentials();
// ApplicationContext appContext = ContextProvider.get(); // IsServerConfig cfg = fetchIsConfig();
// SimpleCredentials credentials = (SimpleCredentials)appContext.authorizationProvider().getCredentials(); // IsServerConfig newConfig = new IsServerConfig(
// cfg.getServerUrl(),
// IsServerConfig cfg = fetchIsConfig(); // cfg.getName(),
// IsServerConfig newConfig = new IsServerConfig( // credentials.getClientID(), // cfg.getClientId(),
// cfg.getServerUrl(), // credentials.getSecret(), // cfg.getClientSecrxet(),
// cfg.getName(), // cfg.getProperties()
// credentials.getClientID(), // cfg.getClientId(), // );
// credentials.getSecret(), // cfg.getClientSecrxet(),
// cfg.getProperties()
// );
// return newConfig;s // return newConfig;s
// } // }
public IsServerConfig fetchIsConfig() throws NotFoundException, ServerException { public IsServerConfig fetchIsConfig() throws NotFoundException, ServerException {
IsServerConfig cfg = InfrastrctureServiceClient.serviceConfigFromIS(RUNTIME_RESOURCE_NAME, CATEGORY, END_POINT_NAME, IS_ROOT_SERVICE); IsServerConfig cfg = InfrastrctureServiceClient.serviceConfigFromIS(RUNTIME_RESOURCE_NAME, CATEGORY,
END_POINT_NAME, IS_ROOT_SERVICE);
return cfg; return cfg;
} }
public static String encodeClientIdContext(String context) { public static String encodeClientIdContext(String context) {
@ -142,12 +144,12 @@ public class KkClientFactory {
// TODO: REMOVE // TODO: REMOVE
// static IsServerConfig getTestConfig() { // static IsServerConfig getTestConfig() {
// String serverUrl = "https://accounts.dev.d4science.org/auth"; // String serverUrl = "https://accounts.dev.d4science.org/auth";
// String realm = "d4science"; // String realm = "d4science";
// String clientId = "id.d4science.org"; // String clientId = "id.d4science.org";
// String clientSecret = ""; // String clientSecret = "";
// return new IsServerConfig(serverUrl, realm, clientId, clientSecret); // return new IsServerConfig(serverUrl, realm, clientId, clientSecret);
// } // }
} }

View File

@ -18,7 +18,7 @@ public class ForbiddenExceptionMapper implements ExceptionMapper<ForbiddenExcept
@Override @Override
public Response toResponse(ForbiddenException exception) { public Response toResponse(ForbiddenException exception) {
Status status = Status.INTERNAL_SERVER_ERROR; Status status = Status.INTERNAL_SERVER_ERROR;
String exceptionMessage = exception.getMessage(); String exceptionMessage = exception.getMessage();

View File

@ -32,12 +32,8 @@ import jakarta.ws.rs.core.Response;
@Path("clients") @Path("clients")
public class ClientsAPI { public class ClientsAPI {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ClientsAPI.class); private static final org.slf4j.Logger logger = LoggerFactory.getLogger(ClientsAPI.class);
@GET @GET
@Path("/{name}") @Path("/{name}")
@Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" })
@ -74,58 +70,54 @@ public class ClientsAPI {
} }
} }
public class ClientFromTemplateParams {
public class ClientFromTemplateParams {
String client_name; String client_name;
String client_id; String client_id;
String context; String context;
} }
@POST @POST
@Path("/fromTemplate/{name}") @Path("/fromTemplate/{name}")
@Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" })
public Response createClientFromTemplate( public Response createClientFromTemplate(
@PathParam("name") String template_name, @PathParam("name") String template_name,
ClientFromTemplateParams params ClientFromTemplateParams params) {
) { ResponseBeanMap responseBean = new ResponseBeanMap();
ResponseBeanMap responseBean = new ResponseBeanMap();
try { try {
RealmResource realm = KkClientFactory.getSingleton().getKKRealm(); RealmResource realm = KkClientFactory.getSingleton().getKKRealm();
List<ClientRepresentation> clients = realm.clients().findByClientId(template_name);
if (clients.size() == 0) {
throw new NotFoundException();
}
String id = clients.get(0).getId();
ClientResource clientResource = realm.clients().get(id);
ClientRepresentation client = clientResource.toRepresentation();
UserRepresentation template_account_user = clientResource.getServiceAccountUser(); List<ClientRepresentation> clients = realm.clients().findByClientId(template_name);
client.setId(params.client_id);
client.setName(params.client_name);
responseBean.putResult("client", client); if (clients.size() == 0) {
responseBean.putResult("service_account_user", template_account_user); throw new NotFoundException();
responseBean.setSuccess(true);
ObjectMapper objectMapper = ContextSerializator.getSerializer();
String jsonData = objectMapper.writeValueAsString(responseBean);
return Response.ok(jsonData).build();
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new InternalServerErrorException(e);
} }
String id = clients.get(0).getId();
ClientResource clientResource = realm.clients().get(id);
ClientRepresentation client = clientResource.toRepresentation();
UserRepresentation template_account_user = clientResource.getServiceAccountUser();
client.setId(params.client_id);
client.setName(params.client_name);
responseBean.putResult("client", client);
responseBean.putResult("service_account_user", template_account_user);
responseBean.setSuccess(true);
ObjectMapper objectMapper = ContextSerializator.getSerializer();
String jsonData = objectMapper.writeValueAsString(responseBean);
return Response.ok(jsonData).build();
} catch (JsonProcessingException e) {
e.printStackTrace();
throw new InternalServerErrorException(e);
}
} }
@POST @POST
@Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" })
public Response createClientFromTemplate(ClientRepresentation client) { public Response createClientFromTemplate(ClientRepresentation client) {
@ -154,7 +146,6 @@ public class ClientsAPI {
} }
@POST @POST
@Path("/") @Path("/")
@Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" }) @Produces({ "application/json;charset=UTF-8", "application/vnd.api+json" })

View File

@ -1,6 +1,5 @@
package org.gcube.service.idm.rest; package org.gcube.service.idm.rest;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.gcube.service.idm.IdMManager; import org.gcube.service.idm.IdMManager;
@ -106,8 +105,8 @@ public class RolesAPI {
/** /**
* Returns the list of users with role in the context * Returns the list of users with role in the context
* *
* @param format users response format * @param format users response format
* @param role_name the role * @param role_name the role
* @param firstResult pagination offset * @param firstResult pagination offset
* @param maxResults maximum results size * @param maxResults maximum results size
*/ */

View File

@ -1,6 +1,5 @@
package org.gcube.service.idm.rest; package org.gcube.service.idm.rest;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -447,7 +446,7 @@ public class UserAPI {
* firstName, * firstName,
* if param 'exact' is true * if param 'exact' is true
* @param firstResult pagination offset * @param firstResult pagination offset
* @param maxResults maximum results size * @param maxResults maximum results size
* @param enabled Boolean representing if user is enabled or not * @param enabled Boolean representing if user is enabled or not
* @param email A String contained in email, or the complete email, if * @param email A String contained in email, or the complete email, if
* param 'exact' is true * param 'exact' is true

View File

@ -308,11 +308,11 @@ public class UsersSocialAPI {
List<UserRepresentation> users = users_resource.search(emailVerified, firstResult, maxResults, enabled, List<UserRepresentation> users = users_resource.search(emailVerified, firstResult, maxResults, enabled,
true); true);
Map<String, String> usernamesAndFullnames = new HashMap<String, String>(); Map<String, String> usernamesAndFullnames = new HashMap<String, String>();
users.forEach(user -> usernamesAndFullnames.put(user.getUsername(), user.getEmail()));
responseBean.setResult(usernamesAndFullnames);
users.forEach(user -> usernamesAndFullnames.put(user.getUsername(), user.getEmail()));
responseBean.setResult(usernamesAndFullnames);
responseBean.setSuccess(true); responseBean.setSuccess(true);
} catch (Exception e) { } catch (Exception e) {
logger.error("Unable to retrieve users", e); logger.error("Unable to retrieve users", e);

View File

@ -1,11 +1,13 @@
/** /**
* <h1>Identity Manager (IDM) Service</h1> * <h1>Identity Manager (IDM) Service</h1>
* *
* <p>Welcome to Identity Manager Service (aka IDM) API documentation.</p> * <p>
* Welcome to Identity Manager Service (aka IDM) API documentation.
* </p>
* *
* <p> * <p>
* To get a complete overview of gCat service take a look at * To get a complete overview of gCat service take a look at
* <a href="../docs/index.html">wiki page</a>. * <a href="../docs/index.html">wiki page</a>.
* </p> * </p>
* *
* *

View File

@ -1,32 +1,37 @@
package org.gcube.service.rest; package org.gcube.service.rest;
public class ErrorMessages public class ErrorMessages {
{
public static final String ERROR_IN_API_RESULT = "The error is reported into the 'message' field of the returned object"; public static final String ERROR_IN_API_RESULT = "The error is reported into the 'message' field of the returned object";
public static final String INVALID_ATTRIBUTE = "Such an attribute doesn't exist"; public static final String INVALID_ATTRIBUTE = "Such an attribute doesn't exist";
public static final String NOT_USER_TOKEN_CONTEXT_USED = "User's information can only be retrieved through a user token (not qualified)"; public static final String NOT_USER_TOKEN_CONTEXT_USED = "User's information can only be retrieved through a user token (not qualified)";
public static final String USER_NOT_AUTHORIZED_PRIVATE = "User is not authorized to access private data"; public static final String USER_NOT_AUTHORIZED_PRIVATE = "User is not authorized to access private data";
public static final String CANNOT_RETRIEVE_PROFILE = "Unable to retrieve user profile"; public static final String CANNOT_RETRIEVE_PROFILE = "Unable to retrieve user profile";
//
// protected static final String CANNOT_RETRIEVE_SERVICE_ENDPOINT_INFORMATION =
// "Unable to retrieve such service endpoint information";
// // private static final String NO_RUNTIME_RESOURCE_TEMPLATE_NAME_CATEGORY =
// protected static final String CANNOT_RETRIEVE_SERVICE_ENDPOINT_INFORMATION = "Unable to retrieve such service endpoint information"; // "There is no Runtime Resource having name %s and Category %s in this scope";
// private static final String NO_RUNTIME_RESOURCE_TEMPLATE_NAME_CATEGORY = "There is no Runtime Resource having name %s and Category %s in this scope";
// public static final String MISSING_TOKEN = "Missing token."; // public static final String MISSING_TOKEN = "Missing token.";
// public static final String MISSING_PARAMETERS = "Missing request parameters."; // public static final String MISSING_PARAMETERS = "Missing request
// parameters.";
// public static final String INVALID_TOKEN = "Invalid token."; // public static final String INVALID_TOKEN = "Invalid token.";
// public static final String TOKEN_GENERATION_APP_FAILED = "Token generation failed."; // public static final String TOKEN_GENERATION_APP_FAILED = "Token generation
// public static final String NOT_APP_TOKEN = "Invalid token: not belonging to an application."; // failed.";
// public static final String NOT_APP_ID = "Invalid application id: it doesn't belong to an application."; // public static final String NOT_APP_TOKEN = "Invalid token: not belonging to
// public static final String NO_APP_PROFILE_FOUND = "There is no application profile for this app id/scope."; // an application.";
// public static final String BAD_REQUEST = "Please check the parameter you passed, it seems a bad request"; // public static final String NOT_APP_ID = "Invalid application id: it doesn't
// public static final String POST_OUTSIDE_VRE = "A post cannot be written into a context that is not a VRE"; // belong to an application.";
// public static final String DEPRECATED_METHOD = "This method is deprecated, must use version 2"; // public static final String NO_APP_PROFILE_FOUND = "There is no application
// profile for this app id/scope.";
// public static final String BAD_REQUEST = "Please check the parameter you
// passed, it seems a bad request";
// public static final String POST_OUTSIDE_VRE = "A post cannot be written into
// a context that is not a VRE";
// public static final String DEPRECATED_METHOD = "This method is deprecated,
// must use version 2";
} }

View File

@ -22,7 +22,7 @@ public class ResponseBeanMap extends ResponseBean {
/** /**
* The result object of the request * The result object of the request
*/ */
protected Map<String, Object>result = new HashMap<String, Object>(); protected Map<String, Object> result = new HashMap<String, Object>();
public ResponseBeanMap() { public ResponseBeanMap() {
super(); super();
@ -64,7 +64,7 @@ public class ResponseBeanMap extends ResponseBean {
this.result = mapResults; this.result = mapResults;
} }
public void putResult(String key, Object res){ public void putResult(String key, Object res) {
this.result.put(key, res); this.result.put(key, res);
} }