package be.iminds.ilabt.jfed.lowlevel.api;

import be.iminds.ilabt.jfed.log.Logger;
import be.iminds.ilabt.jfed.lowlevel.*;
import be.iminds.ilabt.jfed.lowlevel.connection.SfaConnection;
import be.iminds.ilabt.jfed.preferences.JFedPreferences;
import be.iminds.ilabt.jfed.util.GeniUrn;
import be.iminds.ilabt.jfed.util.RFC3339Util;
import be.iminds.ilabt.jfed.util.TextUtil;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;

/**
 * FederationSliceAuthorityApi2
 */
public class FederationSliceAuthorityApi2 extends AbstractFederationApi2 {
    public static final String TYPE_SLICE = "SLICE";
    public static final String TYPE_PROJECT = "PROJECT";
    public static final String ROLE_MEMBER = "MEMBER";
    protected static final String TYPE_SLIVER_INFO = "SLIVER_INFO";
    protected static final String FIELD_SLICE_NAME = "SLICE_NAME";
    protected static final String FIELD_PROJECT_NAME = "PROJECT_NAME";
    protected static final String FIELD_SLIVER_INFO_SLICE_URN = "SLIVER_INFO_SLICE_URN";
    protected static final String FIELD_SLIVER_INFO_URN = "SLIVER_INFO_URN";
    protected static final String FIELD_SLIVER_INFO_AGGREGATE_URN = "SLIVER_INFO_AGGREGATE_URN";
    protected static final String FIELD_SLIVER_INFO_CREATOR_URN = "SLIVER_INFO_CREATOR_URN";
    protected static final String FIELD_SLIVER_INFO_EXPIRATION = "SLIVER_INFO_EXPIRATION";
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(FederationSliceAuthorityApi2.class);
    private static final List<String> API_OBJECTS =
            Collections.unmodifiableList(Arrays.asList(TYPE_SLICE, TYPE_PROJECT, TYPE_SLIVER_INFO));
    private static final List<String> OPTIONAL_API_SERVICES = Collections.unmodifiableList(Arrays.asList(
            TYPE_PROJECT,
            "SLICE_MEMBER",
            "PROJECT_MEMBER",
            TYPE_SLIVER_INFO
    ));
    private static final List<GetVersionResult.FieldInfo> MINIMUM_SLICE_FIELDS =
            Collections.unmodifiableList(Arrays.asList(
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_URN", GetVersionResult.FieldInfo.FieldType.URN, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_UID", GetVersionResult.FieldInfo.FieldType.UID, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_CREATION", GetVersionResult.FieldInfo.FieldType.DATETIME, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, false, false),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_EXPIRATION", GetVersionResult.FieldInfo.FieldType.DATETIME, GetVersionResult.FieldInfo.CreationAllowed.ALLOWED, false, true),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_EXPIRED", GetVersionResult.FieldInfo.FieldType.BOOLEAN, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, FIELD_SLICE_NAME, GetVersionResult.FieldInfo.FieldType.STRING, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, false, false),
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_DESCRIPTION", GetVersionResult.FieldInfo.FieldType.STRING, GetVersionResult.FieldInfo.CreationAllowed.ALLOWED, false, true),

                    //WARNING: special field: SLICE_PROJECT_URN only exists when the server supports the PROJECT service!
                    new GetVersionResult.FieldInfo(TYPE_SLICE, "SLICE_PROJECT_URN", GetVersionResult.FieldInfo.FieldType.URN, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, true, false)

            ));
    private static final List<GetVersionResult.FieldInfo> MINIMUM_PROJECT_FIELDS =
            Collections.unmodifiableList(Arrays.asList(
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_URN", GetVersionResult.FieldInfo.FieldType.URN, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_UID", GetVersionResult.FieldInfo.FieldType.UID, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_CREATION", GetVersionResult.FieldInfo.FieldType.DATETIME, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, false, false),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_EXPIRATION", GetVersionResult.FieldInfo.FieldType.DATETIME, GetVersionResult.FieldInfo.CreationAllowed.ALLOWED, false, true),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_EXPIRED", GetVersionResult.FieldInfo.FieldType.BOOLEAN, GetVersionResult.FieldInfo.CreationAllowed.NOT_ALLOWED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, FIELD_PROJECT_NAME, GetVersionResult.FieldInfo.FieldType.STRING, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, false, false),
                    new GetVersionResult.FieldInfo(TYPE_PROJECT, "PROJECT_DESCRIPTION", GetVersionResult.FieldInfo.FieldType.STRING, GetVersionResult.FieldInfo.CreationAllowed.ALLOWED, false, true)

            ));
    private static final List<GetVersionResult.FieldInfo> MINIMUM_SLIVER_INFO_FIELDS =
            Collections.unmodifiableList(Arrays.asList(
                    new GetVersionResult.FieldInfo(TYPE_SLIVER_INFO, FIELD_SLIVER_INFO_SLICE_URN, GetVersionResult.FieldInfo.FieldType.URN, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_SLIVER_INFO, FIELD_SLIVER_INFO_URN, GetVersionResult.FieldInfo.FieldType.URN, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_SLIVER_INFO, FIELD_SLIVER_INFO_AGGREGATE_URN, GetVersionResult.FieldInfo.FieldType.URN, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_SLIVER_INFO, FIELD_SLIVER_INFO_CREATOR_URN, GetVersionResult.FieldInfo.FieldType.URN, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, true, false),
                    new GetVersionResult.FieldInfo(TYPE_SLIVER_INFO, FIELD_SLIVER_INFO_EXPIRATION, GetVersionResult.FieldInfo.FieldType.DATETIME, GetVersionResult.FieldInfo.CreationAllowed.REQUIRED, false, true),
                    new GetVersionResult.FieldInfo(TYPE_SLIVER_INFO, "SLIVER_INFO_CREATION", GetVersionResult.FieldInfo.FieldType.DATETIME, GetVersionResult.FieldInfo.CreationAllowed.ALLOWED, false, false)

            ));

    public FederationSliceAuthorityApi2(@Nonnull Logger logger, @Nonnull RetrySettings retrySettings, @Nonnull JFedPreferences jFedPreferences) {
        super(logger, retrySettings, new ApiInfo.Api(ApiInfo.ApiName.GENI_CH_SA, 2), jFedPreferences);
    }

    public FederationSliceAuthorityApi2(@Nonnull Logger logger, @Nonnull JFedPreferences jFedPreferences) {
        this(logger, jFedPreferences.getRetrySettings(), jFedPreferences);
    }

    /**
     * A human readable name for the implemented API
     *
     * @return "Uniform Federation Slice Authority API v2";
     */
    static public String getApiName() {
        return "Uniform Federation Slice Authority API v2";
    }

    @Nonnull
    private static List<UrnRoleTuple> processUrnRoleTuplesReply(Object resultValueObject, String typeName, String urnKeyName, String roleKeyName) throws BadReplyGeniException {
        List<Map> resDicts = apiSpecifiesListOfT(Map.class, resultValueObject);
        List<UrnRoleTuple> resList = new ArrayList<>();
        for (Map dict : resDicts) {
            resList.add(new UrnRoleTuple(dict, typeName, urnKeyName, roleKeyName));
        }
        return resList;
    }

    /**
     * A human readable name for the implemented API
     *
     * @return "Uniform Federation Slice Authority API v2";
     */
    @Override
    public String getName() {
        return getApiName();
    }

    @ApiMethod(order = 1, hint = "get_version call: Provide a structure detailing the version information as well as details of accepted options s for CH API calls.", unprotected = true)
    @Nonnull
    public FederationApiReply<GetVersionSAResult> getVersion(@Nonnull SfaConnection con) throws JFedException {
        return executeAndLogXmlRpcCommandGeni(null, con, "getVersion", "get_version",
                new ArrayList<>(), GetVersionSAResult::new);
    }

    @Nonnull
    @Override
    public List<GetVersionResult.FieldInfo> getMinimumFields(String objectName) {
        if (objectName.equalsIgnoreCase(TYPE_SLICE)) {
            return MINIMUM_SLICE_FIELDS;
        } else if (objectName.equalsIgnoreCase(TYPE_PROJECT)) {
            return MINIMUM_PROJECT_FIELDS;
        } else if (objectName.equalsIgnoreCase(TYPE_SLIVER_INFO)) {
            return MINIMUM_SLIVER_INFO_FIELDS;
        } else

            throw new IllegalArgumentException(objectName + " is invalid");
    }

    @Override
    public List<String> getApiObjects() {
        return API_OBJECTS;
    }

    @Override
    public List<String> getOptionalApiServices() {
        return OPTIONAL_API_SERVICES;
    }

    @Override
    public List<String> getRequiredApiServices() {
        return Collections.singletonList(TYPE_SLICE);
    }

    @ApiMethod(order = 2, hint = "lookup call: Lookup information about objects matching given criteria", unprotected = true)
    @Nonnull
    public FederationApiReply<LookupResult<?>> lookup(@Nonnull SfaConnection con,
                                                      @ApiMethodParameter(name = "type", hint = "object type to lookup",
                                                              guiDefault = TYPE_SLICE, parameterType = ApiMethodParameterType.CH_API2_OBJECT_TYPE)
                                                      @Nonnull String type,
                                                      @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                      @Nonnull List<AnyCredential> credentialList,
                                                      @ApiMethodParameter(name = "match", required = false, parameterType = ApiMethodParameterType.CH_API2_MATCH)
                                                      @Nullable Map<String, ?> match,
                                                      @ApiMethodParameter(name = "filter", required = false, parameterType = ApiMethodParameterType.CH_API2_FILTER)
                                                      @Nullable List<String> filter,
                                                      @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                      @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("type", type,
                        "credentialList", credentialList,
                        "match", match,
                        "filter", filter,
                        "extraOptions", extraOptions);

        ReplyConverter replyConverter;
        switch (type) {
            case TYPE_SLICE: {
                replyConverter = SliceInfoList::new;
                break;
            }

            case TYPE_SLIVER_INFO: {
                replyConverter = SliverInfoList::new;
                break;
            }
            case TYPE_PROJECT: {
                replyConverter = ProjectInfoList::new;
                break;
            }
            default: {
                replyConverter = HTLookupResult::new;
                break;
            }
        }

        //noinspection unchecked
        return internalLookup(methodParams, con, "lookup", type, credentialList,
                match, filter, extraOptions, null, replyConverter);
    }

    @ApiMethod(order = 3, hint = "update call: Update information about object")
    @Nonnull
    public FederationApiReply<String> update(@Nonnull SfaConnection con,
                                             @ApiMethodParameter(name = "type", hint = "object type to update",
                                                     guiDefault = TYPE_SLICE, parameterType = ApiMethodParameterType.CH_API2_OBJECT_TYPE)
                                             @Nonnull String type,
                                             @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                             @Nonnull List<AnyCredential> credentialList,
                                             @ApiMethodParameter(name = "urn", parameterType = ApiMethodParameterType.USER_URN)
                                             @Nonnull String urn,
                                             @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API2_FIELDS)
                                             @Nonnull Map<String, String> fields,
                                             @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                             @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("type", type,
                        "credentialList", credentialList,
                        "urn", urn,
                        "fields", fields,
                        "extraOptions", extraOptions);
        return update_internal(methodParams, con, "update", type, credentialList, urn, null/*urntype*/, fields, extraOptions);
    }

    @ApiMethod(order = 4, hint = "create method")
    @Nonnull
    public FederationApiReply<?> create(@Nonnull SfaConnection con,
                                        @ApiMethodParameter(name = "type", hint = "object type to create",
                                                guiDefault = TYPE_SLICE, parameterType = ApiMethodParameterType.CH_API2_OBJECT_TYPE)
                                        @Nonnull String type,
                                        @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                        @Nonnull List<AnyCredential> credentialList,
                                        @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API2_FIELDS)
                                        @Nonnull Map<String, String> fields,
                                        @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                        @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("type", type,
                        "credentialList", credentialList,
                        "type", type,
                        "fields", fields,
                        "extraOptions", extraOptions);

        ReplyConverter replyConverter;
        switch (type) {
            case TYPE_SLICE:
                replyConverter = SliceInfo::new;
                break;
            case TYPE_SLIVER_INFO:
                replyConverter = SliverInfo::new;
                break;
            case TYPE_PROJECT:
                replyConverter = ProjectInfo::new;
                break;
            default:
                replyConverter = AbstractApi::apiSpecifiesMapStringToObject;
        }

        //noinspection unchecked
        return create_internal(methodParams, con, "create", type, credentialList, fields, extraOptions, replyConverter);
    }


    @ApiMethod(order = 5, hint = "delete call: delete an OBJECT")
    @Nonnull
    public FederationApiReply<Boolean> delete(@Nonnull SfaConnection con,
                                              @ApiMethodParameter(name = "type", hint = "object type to delete",
                                                      guiDefault = TYPE_SLICE, parameterType = ApiMethodParameterType.CH_API2_OBJECT_TYPE)
                                                      String type,
                                              @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                              @Nonnull List<AnyCredential> credentialList,
                                              @ApiMethodParameter(name = "urnToDelete", parameterType = ApiMethodParameterType.STRING)
                                              @Nonnull String urnToDelete,
                                              @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                              @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("type", type,
                        "credentialList", credentialList,
                        "urnToDelete", urnToDelete,
                        "extraOptions", extraOptions);
        return delete_internal(methodParams, con, "delete", type, credentialList, urnToDelete, extraOptions);
    }


    @ApiMethod(order = 10, hint = "Convenience method for lookup call: Lookup information about SLICEs matching given criteria", unprotected = true, convenienceMethodFor = "lookup", convenienceMethodObjectType = TYPE_SLICE)
    @Nonnull
    public FederationApiReply<SliceInfoList> lookupSlice(@Nonnull SfaConnection con,
                                                         @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                         @Nonnull List<AnyCredential> credentialList,
                                                         @ApiMethodParameter(name = "match", required = false, parameterType = ApiMethodParameterType.CH_API2_MATCH)
                                                         @Nullable Map<String, ?> match,
                                                         @ApiMethodParameter(name = "filter", required = false, parameterType = ApiMethodParameterType.CH_API2_FILTER)
                                                         @Nullable List<String> filter,
                                                         @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                         @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "match", match,
                        "filter", filter,
                        "extraOptions", extraOptions);
        return internalLookup(methodParams, con, "lookup", TYPE_SLICE,
                credentialList, match, filter, extraOptions, null, SliceInfoList::new);
    }


    @ApiMethod(order = 11, hint = "Convenience method for update call: Update SLICE information", convenienceMethodFor = "update", convenienceMethodObjectType = TYPE_SLICE)
    @Nonnull
    public FederationApiReply<String> updateSlice(@Nonnull SfaConnection con,
                                                  @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                  @Nonnull List<AnyCredential> credentialList,
                                                  @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.USER_URN)
                                                  @Nonnull GeniUrn sliceUrn,
                                                  @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API2_FIELDS)
                                                  @Nonnull Map<String, String> fields,
                                                  @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                  @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "sliceUrn", sliceUrn,
                        "fields", fields,
                        "extraOptions", extraOptions);
        return update_internal(methodParams, con, "update", TYPE_SLICE, credentialList, sliceUrn.getValue(), "member", fields, extraOptions);
    }


    @ApiMethod(order = 12, hint = "Convenience method for create call: create SLICE", convenienceMethodFor = "create", convenienceMethodObjectType = TYPE_SLICE)
    @Nonnull
    public FederationApiReply<SliceInfo> createSlice(@Nonnull SfaConnection con,
                                                     @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                     @Nonnull List<AnyCredential> credentialList,
                                                     @ApiMethodParameter(name = "sliceName", parameterType = ApiMethodParameterType.STRING)
                                                     @Nonnull String sliceName,
                                                     @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API2_FIELDS)
                                                     @Nonnull Map<String, String> fields,
                                                     @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                     @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "sliceName", sliceName,
                        "fields", fields,
                        "extraOptions", extraOptions);

        Map<String, String> allFields = new HashMap<>(fields);
        assert !allFields.containsKey(FIELD_SLICE_NAME) :
                "Add SLICE_NAME with the sliceName option, not directly in the \"fields\" argument";
        allFields.put(FIELD_SLICE_NAME, sliceName);

        return create_internal(methodParams, con, "createSlice", TYPE_SLICE, credentialList, allFields, extraOptions, SliceInfo::new);
    }

    //There is no delete SLICE, as slice deletion is never allowed


    @ApiMethod(order = 15, hint = "get_credentials call: Provide list of credentials (signed statements) for given slice")
    @Nonnull
    public FederationApiReply<List<AnyCredential>> getSliceCredentials(@Nonnull SfaConnection con,
                                                                       @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                       @Nonnull List<AnyCredential> credentialList,
                                                                       @ApiMethodParameter(name = "sliceUrn", parameterType = ApiMethodParameterType.SLICE_URN)
                                                                       @Nonnull GeniUrn sliceUrn,
                                                                       @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                       @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("credentialList", credentialList,
                "sliceUrn", sliceUrn,
                "extraOptions", extraOptions);

        if (!Objects.equals(sliceUrn.getEncodedResourceType(), "slice"))
            LOG.warn("slice URN argument to getCredentials is not a valid slice urn: \"" + sliceUrn + "\" (will be used anyway)");

        return getCredentials_internal(methodParams, con, "getSliceCredentials", TYPE_SLICE, credentialList, sliceUrn, extraOptions);
    }


    @ApiMethod(order = 16, hint = "get_credentials call: Provide list of credentials (signed statements) (generic call)")
    @Nonnull
    public FederationApiReply<List<AnyCredential>> getCredentials(@Nonnull SfaConnection con,
                                                                  @ApiMethodParameter(name = "objectTypeName", parameterType = ApiMethodParameterType.STRING)
                                                                  @Nonnull String objectTypeName,
                                                                  @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                  @Nonnull List<AnyCredential> credentialList,
                                                                  @ApiMethodParameter(name = "urn", parameterType = ApiMethodParameterType.URN)
                                                                  @Nonnull GeniUrn urn,
                                                                  @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                          Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("objectTypeName", objectTypeName,
                        "credentialList", credentialList,
                        "urn", urn,
                        "extraOptions", extraOptions);

        return getCredentials_internal(methodParams, con, "getCredentials", objectTypeName, credentialList, urn, extraOptions);
    }


    @ApiMethod(order = 20, hint = "Convenience method for lookup call: Lookup information about SLIVER_INFOs matching given criteria", unprotected = true, convenienceMethodFor = "lookup", convenienceMethodObjectType = TYPE_SLIVER_INFO)
    @Nonnull
    public FederationApiReply<SliverInfoList> lookupSliverInfo(@Nonnull SfaConnection con,
                                                               @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                               @Nonnull List<AnyCredential> credentialList,
                                                               @ApiMethodParameter(name = "sliceUrn", hint = "convenience argument: SLIVER_INFO_SLICE_URN to add to match argument", parameterType = ApiMethodParameterType.SLICE_URN)
                                                               @Nullable GeniUrn sliceUrn,
                                                               @ApiMethodParameter(name = "match", required = false, parameterType = ApiMethodParameterType.CH_API2_MATCH)
                                                               @Nullable Map<String, ?> match,
                                                               @ApiMethodParameter(name = "filter", required = false, parameterType = ApiMethodParameterType.CH_API2_FILTER)
                                                               @Nullable List<String> filter,
                                                               @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                               @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "sliceUrn", sliceUrn,
                        "match", match,
                        "filter", filter,
                        "extraOptions", extraOptions);

        Map<String, Object> matchArg = match == null ? null : new HashMap<>(match);
        if (sliceUrn != null) {
            if (matchArg == null)
                matchArg = new HashMap<>();
            matchArg.put(FIELD_SLIVER_INFO_SLICE_URN, sliceUrn.getValue());
        }

        return internalLookup(methodParams, con, "lookupSliverInfo",
                TYPE_SLIVER_INFO, credentialList, matchArg, filter, extraOptions, null, SliverInfoList::new);
    }

    @ApiMethod(order = 21, hint = "Convenience method for update call: Update SLIVER_INFO information", convenienceMethodFor = "update", convenienceMethodObjectType = TYPE_SLIVER_INFO)
    @Nonnull
    public FederationApiReply<String> updateSliverInfo(@Nonnull SfaConnection con,
                                                       @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                       @Nonnull List<AnyCredential> credentialList,
                                                       @ApiMethodParameter(name = "urn", parameterType = ApiMethodParameterType.SLIVER_URN)
                                                       @Nonnull String urn,
                                                       @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API2_FIELDS)
                                                       @Nonnull Map<String, String> fields,
                                                       @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                       @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "urn", urn,
                        "fields", fields,
                        "extraOptions", extraOptions);
        return update_internal(methodParams, con, "update", TYPE_SLIVER_INFO, credentialList, urn, "member", fields, extraOptions);
    }

    @ApiMethod(order = 22, hint = "Convenience method for create call: create SLIVER_INFO", convenienceMethodFor = "create", convenienceMethodObjectType = TYPE_SLIVER_INFO)
    @Nonnull
    public FederationApiReply<SliverInfo> createSliverInfo(@Nonnull SfaConnection con,
                                                           @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                           @Nonnull List<AnyCredential> credentialList,
                                                           @ApiMethodParameter(name = "sliceUrn", hint = "convenience argument: add (required) SLIVER_INFO_SLICE_URN to fields option", parameterType = ApiMethodParameterType.SLICE_URN, required = false, guiDefaultOptional = true)
                                                           @Nullable GeniUrn sliceUrn,
                                                           @ApiMethodParameter(name = "sliverUrn", hint = "convenience argument: add (required) SLIVER_INFO_URN to fields option", parameterType = ApiMethodParameterType.SLIVER_URN, required = false, guiDefaultOptional = true)
                                                           @Nullable GeniUrn sliverUrn,
                                                           @ApiMethodParameter(name = "aggregateUrn", hint = "convenience argument: add (required) SLIVER_INFO_AGGREGATE_URN to fields option", parameterType = ApiMethodParameterType.AM_URN, required = false, guiDefaultOptional = true)
                                                           @Nullable GeniUrn aggregateUrn,
                                                           @ApiMethodParameter(name = "creatorUrn", hint = "convenience argument: add (required) SLIVER_INFO_CREATOR_URN to fields option", parameterType = ApiMethodParameterType.USER_URN, required = false, guiDefaultOptional = true)
                                                           @Nullable GeniUrn creatorUrn,
                                                           @ApiMethodParameter(name = "sliverExpirationDate", hint = "convenience argument: add (required) SLIVER_INFO_EXPIRATION  to fields option", parameterType = ApiMethodParameterType.DATE, required = false, guiDefaultOptional = true)
                                                           @Nullable Date sliverExpirationDate,
                                                           @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API2_FIELDS, required = false)
                                                           @Nullable Map<String, String> fields,
                                                           @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                           @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("credentialList", credentialList,
                "sliceUrn", sliceUrn,
                "sliverUrn", sliverUrn,
                "aggregateUrn", aggregateUrn,
                "creatorUrn", creatorUrn,
                "sliverExpirationDate", sliverExpirationDate,
                "fields", fields,
                "extraOptions", extraOptions);

        Map<String, String> allFields =
                fields == null ? new HashMap<>() : new HashMap<>(fields);

        assert allFields.containsKey(FIELD_SLIVER_INFO_SLICE_URN) == (sliceUrn == null) :
                "Either specify sliceUrn (non-null) or add \"SLIVER_INFO_SLICE_URN\" to \"fields\". (exactly 1 is required)";
        assert allFields.containsKey(FIELD_SLIVER_INFO_URN) == (sliverUrn == null) :
                "Either specify sliverUrn (non-null) or add \"SLIVER_INFO_URN\" to \"fields\". (exactly 1 is required)";
        assert allFields.containsKey(FIELD_SLIVER_INFO_AGGREGATE_URN) == (aggregateUrn == null) :
                "Either specify aggregateUrn (non-null) or add \"SLIVER_INFO_AGGREGATE_URN\" to \"fields\". (exactly 1 is required)";
        assert allFields.containsKey(FIELD_SLIVER_INFO_CREATOR_URN) == (creatorUrn == null) :
                "Either specify creatorUrn (non-null) or add \"SLIVER_INFO_CREATOR_URN\" to \"fields\". (exactly 1 is required)";
        assert allFields.containsKey(FIELD_SLIVER_INFO_EXPIRATION) == (sliverExpirationDate == null) :
                "Either specify sliverExpirationDate (non-null) or add \"SLIVER_INFO_EXPIRATION\" to \"fields\". (exactly 1 is required)";

        if (sliceUrn != null)
            allFields.put(FIELD_SLIVER_INFO_SLICE_URN, sliceUrn.toString());
        if (sliverUrn != null)
            allFields.put(FIELD_SLIVER_INFO_URN, sliverUrn.toString());
        if (aggregateUrn != null)
            allFields.put(FIELD_SLIVER_INFO_AGGREGATE_URN, aggregateUrn.toString());
        if (creatorUrn != null)
            allFields.put(FIELD_SLIVER_INFO_CREATOR_URN, creatorUrn.toString());
        if (sliverExpirationDate != null)
            allFields.put(FIELD_SLIVER_INFO_EXPIRATION, RFC3339Util.dateToRFC3339String(sliverExpirationDate));


        //noinspection unchecked
        return create_internal(methodParams, con, "createSliverInfo", TYPE_SLIVER_INFO, credentialList, allFields, extraOptions, SliverInfo::new);
    }

    @ApiMethod(order = 23, hint = "Convenience method for delete call: delete a SLIVER_INFO", convenienceMethodFor = "delete", convenienceMethodObjectType = TYPE_SLIVER_INFO)
    @Nonnull
    public FederationApiReply<Boolean> deleteSliverInfo(@Nonnull SfaConnection con,
                                                        @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                        @Nonnull List<AnyCredential> credentialList,
                                                        @ApiMethodParameter(name = "urnToDelete", parameterType = ApiMethodParameterType.SLIVER_URN)
                                                        @Nonnull String urnToDelete,
                                                        @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false,
                                                                parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                        @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "urnToDelete", urnToDelete,
                        "extraOptions", extraOptions);
        return delete_internal(methodParams, con, "delete", TYPE_SLIVER_INFO, credentialList, urnToDelete, extraOptions);
    }

    @ApiMethod(order = 30, hint = "Convenience method for lookup call: Lookup information about PROJECTs matching given criteria", unprotected = true, convenienceMethodFor = "lookup", convenienceMethodObjectType = TYPE_PROJECT)
    @Nonnull
    public FederationApiReply<ProjectInfoList> lookupProject(@Nonnull SfaConnection con,
                                                                       @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                       @Nonnull List<AnyCredential> credentialList,
                                                                       @ApiMethodParameter(name = "match", required = false, parameterType = ApiMethodParameterType.CH_API2_MATCH)
                                                                       @Nullable Map<String, ?> match,
                                                                       @ApiMethodParameter(name = "filter", required = false, parameterType = ApiMethodParameterType.CH_API2_FILTER)
                                                                       @Nullable List<String> filter,
                                                                       @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                       @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "match", match,
                        "filter", filter,
                        "extraOptions", extraOptions);

        return internalLookup(methodParams, con, "lookupProject",
                TYPE_PROJECT, credentialList, match, filter, extraOptions, null, ProjectInfoList::new);
    }

    @ApiMethod(order = 31, hint = "Convenience method for update call: Update PROJECT information", convenienceMethodFor = "update", convenienceMethodObjectType = TYPE_PROJECT)
    @Nonnull
    public FederationApiReply<String> updateProject(@Nonnull SfaConnection con,
                                                    @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                    @Nonnull List<AnyCredential> credentialList,
                                                    @ApiMethodParameter(name = "projectUrn", parameterType = ApiMethodParameterType.USER_URN)
                                                    @Nonnull GeniUrn projectUrn,
                                                    @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API2_FIELDS)
                                                    @Nonnull Map<String, String> fields,
                                                    @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                    @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "projectUrn", projectUrn,
                        "fields", fields,
                        "extraOptions", extraOptions);
        return update_internal(methodParams, con, "update", TYPE_PROJECT, credentialList, projectUrn.getValue(), "member", fields, extraOptions);
    }

    @ApiMethod(order = 32, hint = "Convenience method for create call: create PROJECT", convenienceMethodFor = "create", convenienceMethodObjectType = TYPE_PROJECT)
    @Nonnull
    public FederationApiReply<ProjectInfo> createProject(@Nonnull SfaConnection con,
                                                         @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                         @Nonnull List<AnyCredential> credentialList,
                                                         @ApiMethodParameter(name = "projectName", required = false, parameterType = ApiMethodParameterType.STRING)
                                                         @Nullable String projectName,
                                                         @ApiMethodParameter(name = "fields", parameterType = ApiMethodParameterType.CH_API2_FIELDS)
                                                         @Nonnull Map<String, String> fields,
                                                         @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false, parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                         @Nullable Map<String, Object> extraOptions) throws JFedException {

        Map<String, Object> methodParams = makeMethodParameters("credentialsList", credentialList,
                "projectName", projectName,
                "fields", fields,
                "extraOptions", extraOptions);

        Map<String, String> allFields = new HashMap<>(fields);
        assert fields.containsKey(FIELD_PROJECT_NAME) == (projectName == null) :
                "Either specify projectName (non-null) or add \"PROJECT_NAME\" to \"fields\". (exactly 1 is required)";
        if (projectName != null)
            allFields.put(FIELD_PROJECT_NAME, projectName);

        return create_internal(methodParams, con, "createProject", TYPE_PROJECT, credentialList, allFields, extraOptions, ProjectInfo::new);
    }

    @ApiMethod(order = 33, hint = "Convenience method for delete call: delete a PROJECT", convenienceMethodFor = "delete", convenienceMethodObjectType = TYPE_PROJECT)
    @Nonnull
    public FederationApiReply<Boolean> deleteProject(@Nonnull SfaConnection con,
                                                     @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                     @Nonnull List<AnyCredential> credentialList,
                                                     @ApiMethodParameter(name = "urnToDelete", parameterType = ApiMethodParameterType.STRING)
                                                     @Nonnull String urnToDelete,
                                                     @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false,
                                                             parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                     @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("credentialList", credentialList,
                        "urnToDelete", urnToDelete,
                        "extraOptions", extraOptions);
        return delete_internal(methodParams, con, "delete", TYPE_PROJECT, credentialList, urnToDelete, extraOptions);
    }

    @ApiMethod(order = 40, hint = "modify_slice_membership call:")
    @Nonnull
    public FederationApiReply<String> modifyMembership(@Nonnull SfaConnection con,
                                                       @ApiMethodParameter(name = "type", hint = "object type for which members need to change",
                                                               guiDefault = TYPE_SLICE, parameterType = ApiMethodParameterType.CH_API2_OBJECT_TYPE)
                                                       @Nonnull String type,
                                                       @ApiMethodParameter(name = "urn", parameterType = ApiMethodParameterType.URN)
                                                       @Nonnull GeniUrn urn,
                                                       @ApiMethodParameter(name = "membersToAdd", parameterType = ApiMethodParameterType.CH_API_LIST_MEMBER_TUPLES)
                                                       @Nullable List<UrnRoleTuple> membersToAdd,
                                                       @ApiMethodParameter(name = "membersToRemove", parameterType = ApiMethodParameterType.LIST_OF_URN_STRING)
                                                       @Nullable List<GeniUrn> membersToRemove,
                                                       @ApiMethodParameter(name = "membersToChange", parameterType = ApiMethodParameterType.CH_API_LIST_MEMBER_TUPLES)
                                                       @Nullable List<UrnRoleTuple> membersToChange,
                                                       @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                       @Nonnull List<AnyCredential> credentialList,
                                                       @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false,
                                                               parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                       @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("type", type,
                "urn", urn,
                "membersToAdd", membersToAdd,
                "membersToRemove", membersToRemove,
                "membersToChange", membersToChange,
                "credentialList", credentialList,
                "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>(4);
        args.add(type);
        args.add(urn.getValue());
        args.add(createCredentialsListWithTypeAndVersion(credentialList));

        Map<String, Object> options = new HashMap<>();
        List<Map> membersToAddList = new ArrayList<>();
        if (membersToAdd != null)
            membersToAddList.addAll(membersToAdd.stream().map(UrnRoleTuple::toMemberTuple).collect(Collectors.toList()));
        options.put("members_to_add", membersToAddList);

        List<String> membersToRemoveList = new ArrayList<>();
        if (membersToRemove != null)
            membersToRemoveList.addAll(membersToRemove.stream().map(GeniUrn::toString).collect(Collectors.toList()));
        options.put("members_to_remove", membersToRemoveList);

        List<Map> membersToChangeList = new ArrayList<>();
        if (membersToChange != null)
            membersToChangeList.addAll(membersToChange.stream().map(UrnRoleTuple::toMemberTuple).collect(Collectors.toList()));
        options.put("members_to_change", membersToChangeList);
        if (extraOptions != null)
            options.putAll(extraOptions);
        args.add(options);

        //should return nothing at all, but if there is anything, we convert it to string and return it
        return executeAndLogXmlRpcCommandGeni(methodParams, con, "modifyMembership", "modify_membership",
                args, STRING_REPLY_CONVERTER);
    }

    /*
    */
    @ApiMethod(order = 41, hint = "lookup_members call:")
    @Nonnull
    public FederationApiReply<List<UrnRoleTuple>> lookupMembers(@Nonnull SfaConnection con,
                                                                @ApiMethodParameter(name = "type", hint = "object type for which members need to change",
                                                                        guiDefault = TYPE_SLICE, parameterType = ApiMethodParameterType.CH_API2_OBJECT_TYPE)
                                                                @Nonnull String type,
                                                                @ApiMethodParameter(name = "urn", parameterType = ApiMethodParameterType.SLICE_URN)
                                                                @Nonnull GeniUrn urn,
                                                                @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                @Nonnull List<AnyCredential> credentialList,
                                                                @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false,
                                                                        parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams = makeMethodParameters("type", type,
                "credentialList", credentialList,
                "urn", urn,
                "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>();
        args.add(type);
        args.add(urn.toString());
        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        args.add(createOptionsMap(extraOptions));

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "lookupMembers", "lookup_members",
                args, resultValueObject -> processUrnRoleTuplesReply(resultValueObject, type, type + "_MEMBER", type + "_ROLE"));
    }


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////// Member Service Methods /////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /*
    */

    /*
    */
    @ApiMethod(order = 42, hint = "lookup_for_member call:")
    @Nonnull
    public FederationApiReply<List<UrnRoleTuple>> lookupForMember(@Nonnull SfaConnection con,
                                                                  @ApiMethodParameter(name = "type", hint = "object type for which members need to change",
                                                                          guiDefault = TYPE_SLICE, parameterType = ApiMethodParameterType.CH_API2_OBJECT_TYPE)
                                                                  @Nonnull String type,
                                                                  @ApiMethodParameter(name = "memberUrn", parameterType = ApiMethodParameterType.USER_URN)
                                                                  @Nonnull GeniUrn memberUrn,
                                                                  @ApiMethodParameter(name = "credentialList", parameterType = ApiMethodParameterType.LIST_OF_CREDENTIAL)
                                                                  @Nonnull List<AnyCredential> credentialList,
                                                                  @ApiMethodParameter(name = "extraOptions", hint = "extra options", required = false,
                                                                          parameterType = ApiMethodParameterType.GENI_EXTRA_OPTIONS)
                                                                  @Nullable Map<String, Object> extraOptions) throws JFedException {
        Map<String, Object> methodParams =
                makeMethodParameters("type", type,
                        "credentialList", credentialList,
                        "memberUrn", memberUrn,
                        "extraOptions", extraOptions);

        List<Object> args = new ArrayList<>();
        args.add(type);
        args.add(memberUrn.toString());
        args.add(createCredentialsListWithTypeAndVersion(credentialList));
        args.add(createOptionsMap(extraOptions));

        return executeAndLogXmlRpcCommandGeni(methodParams, con, "lookupForMember", "lookup_for_member",
                args, resultValueObject -> processUrnRoleTuplesReply(resultValueObject, type, type + "_URN", type + "_ROLE"));
    }

    public static class GetVersionSAResult extends GetVersionResult {
        private final List<String> services;//only for Slice Authority
        private final List<String> roles;//only for Slice Authority

        public GetVersionSAResult(Object resultValueObject) throws BadReplyGeniException {
            super(resultValueObject, true/*v2*/);
            Map<String, Object> versionInfo = apiSpecifiesMapStringToObject(resultValueObject);
            services = new ArrayList<>(apiSpecifiesListOfString(versionInfo.get("SERVICES")));

            //roles is optional
            if (versionInfo.get("ROLES") != null)
                roles = new ArrayList<>(apiSpecifiesListOfString(versionInfo.get("ROLES")));
            else
                roles = Collections.emptyList();
        }

        public List<String> getServices() {
            return services;
        }

        public List<String> getRoles() {
            return roles;
        }

        @Override
        public String toString() {
            return "GetVersionSAResult{" +
                    "version='" + version + '\'' +
                    ", supportedCredentialTypes=" + supportedCredentialTypes +
                    ", fieldInfos=" + fieldInfos +
                    ", services=" + services +
                    ", roles=" + roles +
                    '}';
        }
    }

    public static class UrnRoleTuple {
        @Nonnull
        public final GeniUrn urn;
        @Nonnull
        public final String typeName;
        @Nonnull
        public final String role;
        @Nullable
        public final Map dictRaw;

        public UrnRoleTuple(@Nonnull String typeName, @Nonnull GeniUrn urn, @Nonnull String role) {
            this.typeName = typeName;
            this.dictRaw = null;
            this.urn = urn;
            this.role = role;
        }


        public UrnRoleTuple(@Nonnull Map dictRaw, @Nonnull String typeName, @Nonnull String urnKeyName, @Nonnull String roleKeyName) throws BadReplyGeniException {
            this.typeName = typeName;
            Map<String, Object> dict = apiSpecifiesMapStringToObject(dictRaw);
            String urnString = (String) dict.get(urnKeyName);
            if (urnString == null)
                throw new BadReplyGeniException("Dictionary should contain keys '" + urnKeyName + "' and '" + roleKeyName + ". It contains only: " + dict.keySet());

            try {
                this.urn = new GeniUrn(urnString);
            } catch (GeniUrn.GeniUrnParseException e) {
                throw new BadReplyGeniException("Could not parse urn '" + urnString + "'", e);
            }
            this.role = (String) dict.get(roleKeyName);
            if (role == null)
                throw new BadReplyGeniException("Dictionary should contain keys '" + urnKeyName + "' and '" + roleKeyName + ". It contains only: " + dict.keySet());
            this.dictRaw = dictRaw;
        }


        public Map toMemberTuple() {
            Map<String, String> v = new HashMap<>();
            v.put(typeName + "_MEMBER", urn.toString());
            v.put(typeName + "_ROLE", role);
            return v;
        }

        @Nonnull
        public GeniUrn getUrn() {
            return urn;
        }

        @Nonnull
        public String getRole() {
            return role;
        }

        public boolean isExpired() {
            return dictRaw != null && dictRaw.containsKey("EXPIRED") && TextUtil.objectToBoolean(dictRaw.get("EXPIRED"));
        }

        @Override
        public String toString() {
            return "UrnRoleTuple{" +
                    "urn=\"" + urn +
                    "\", role=\"" + role + "\"" +
                    '}';
        }
    }
}
