org.unitedid.shibboleth.attribute.resolver.provider.dataConnector.MongoDbDataConnector.java Source code

Java tutorial

Introduction

Here is the source code for org.unitedid.shibboleth.attribute.resolver.provider.dataConnector.MongoDbDataConnector.java

Source

/*
 * Copyright (c) 2011 United ID.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.unitedid.shibboleth.attribute.resolver.provider.dataConnector;

import com.mongodb.*;
import com.mongodb.util.JSON;
import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
import edu.internet2.middleware.shibboleth.common.attribute.provider.BasicAttribute;
import edu.internet2.middleware.shibboleth.common.attribute.resolver.AttributeResolutionException;
import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.ShibbolethResolutionContext;
import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.BaseDataConnector;
import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.TemplateEngine;
import edu.internet2.middleware.shibboleth.common.profile.provider.SAMLProfileRequestContext;
import edu.internet2.middleware.shibboleth.common.session.LogoutEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

import java.util.*;

/**
 * <code>MongoDbDataConnector</code> provides a plugin to fetch attributes from a mongo database.
 *
 * @author Stefan Wold <stefan.wold@unitedid.org>
 */
public class MongoDbDataConnector extends BaseDataConnector implements ApplicationListener {

    /** Class logger. */
    private final Logger log = LoggerFactory.getLogger(MongoDbDataConnector.class);

    /** Indicates if this connector has been initialized. */
    private boolean initialized;

    /** A list of mongo database servers. */
    private List<ServerAddress> mongoHost;

    /** Mongo database. */
    private String mongoDbName;

    /** Mongo collection */
    private String mongoCollection;

    /** Username */
    private String mongoUser;

    /** Password */
    private String mongoPassword;

    /** Preferred read preference valid values */
    private static final Map<String, ReadPreference> MONGO_READ_PREF = Collections
            .unmodifiableMap(new HashMap<String, ReadPreference>() {
                {
                    put("primary", ReadPreference.primary());
                    put("primaryPreferred", ReadPreference.primaryPreferred());
                    put("secondary", ReadPreference.secondary());
                    put("secondaryPreferred", ReadPreference.secondaryPreferred());
                    put("nearest", ReadPreference.nearest());
                }
            });

    /** Preferred read preference for MongoDB */
    private String preferredRead;

    /** Whether the query results should be cached */
    private boolean cacheResults;

    /** Data cache */
    private Map<String, Map<String, Map<String, BaseAttribute>>> dataCache;

    /** Mapping object between mongo document keys and shibboleth attributes */
    private Map<String, MongoDbKeyAttributeMapper> keyAttributeMap;

    /** DB ojbect. */
    private DB db;

    /** Template name that produces the query to use. */
    private String queryTemplateName;

    /** Template that produces the query to use. */
    private String queryTemplate;

    /** Template engine that create a real query from the query template. */
    private TemplateEngine queryCreator;

    /** Whether persistent ID mode should be used. */
    private boolean usePersistentId;

    /** ID of the attribute generated by this data connector. */
    private String pidGeneratedAttributeId;

    /** ID of the attribute whose first value  is used when generating a persistent ID. */
    private String pidSourceAttributeId;

    /**
     * Constructor
     *
     * @param hosts the list of hosts to connect to
     * @param dbName the mongo database name to use
     * @param collection the mongo collection name to use
     */
    public MongoDbDataConnector(List<ServerAddress> hosts, String dbName, String collection) {
        super();
        mongoHost = hosts;
        mongoDbName = dbName;
        mongoCollection = collection;
        keyAttributeMap = new HashMap<String, MongoDbKeyAttributeMapper>();
    }

    /**
     * Initializes the connector
     */
    public void initialize() {
        initialized = true;
        registerTemplate();
        initializeMongoDbConnection();
        initializeCache();
    }

    /**
     * Creates the mongo database connection
     */
    protected void initializeMongoDbConnection() {
        if (initialized) {
            log.debug("MongoDB connector initializing!");
            Mongo mongoCon = new Mongo(mongoHost);
            mongoCon.setReadPreference(getPreferredRead());
            db = mongoCon.getDB(mongoDbName);

            if (getMongoUser() != null && getMongoPassword() != null) {
                boolean dbAuth = db.authenticate(getMongoUser(), getMongoPassword().toCharArray());
                if (!dbAuth) {
                    log.error(
                            "MongoDB data connector {} authentication failed for database {}, username or password!",
                            getId(), mongoDbName);
                    throw new MongoException("MongoDB data connector " + getId() + " authentication failed!");
                } else {
                    log.debug("MongoDB data connector {} authentication successful!", getId());
                }
            }
        }
    }

    /**
     * Initialize search results cache
     */
    protected void initializeCache() {
        if (cacheResults && initialized) {
            dataCache = new HashMap<String, Map<String, Map<String, BaseAttribute>>>();
        }
    }

    /**
     * Reset search results cache
     */
    protected void resetCache() {
        if (cacheResults && initialized) {
            dataCache.clear();
        }
    }

    /**
     * Registers the query template with template engine.
     */
    protected void registerTemplate() {
        if (initialized) {
            queryTemplateName = "shibboleth.resolver.dc." + getId();
            queryCreator.registerTemplate(queryTemplateName, queryTemplate);
        }
    }

    /** {@inheritDoc} */
    public Map<String, BaseAttribute> resolve(ShibbolethResolutionContext resolutionContext)
            throws AttributeResolutionException {
        String query;
        if (isPersistentId()) {
            query = "persistentId";
        } else {
            query = queryCreator.createStatement(queryTemplateName, resolutionContext, getDependencyIds(), null);
        }
        log.debug("Data connector {} search query: {}", getId(), query);

        Map<String, BaseAttribute> resolvedAttributes = new HashMap<String, BaseAttribute>();

        //TODO: Implement cache timeout
        if (cacheResults) {
            log.debug("Data connector {} checking cache for attributes.", getId());
            resolvedAttributes = getAttributeCache(resolutionContext, query);
        }

        if (resolvedAttributes == null || resolvedAttributes.isEmpty()) {
            log.debug("Data connector {} persistent id mode: {}", getId(), isPersistentId());
            if (isPersistentId()) {
                resolvedAttributes = retrievePersistentIdAttribute(resolutionContext);
            } else {
                resolvedAttributes = retrieveAttributesFromDatabase(query);
            }

            if (cacheResults && resolvedAttributes != null) {
                setAttributeCache(resolutionContext, query, resolvedAttributes);
            }
        }

        return resolvedAttributes;
    }

    /**
     * Retrieves cached attributes for current resolution context
     *
     * @param resolutionContext <code>ResolutionContext</code>
     * @param query the query used to get the search result
     * @return cached attributes
     */
    protected Map<String, BaseAttribute> getAttributeCache(ShibbolethResolutionContext resolutionContext,
            String query) {
        if (cacheResults) {
            String principal = resolutionContext.getAttributeRequestContext().getPrincipalName();
            Map<String, Map<String, BaseAttribute>> cache = dataCache.get(principal);
            if (cache != null) {
                Map<String, BaseAttribute> attributes = cache.get(query);
                log.debug("Data connector {} got cached attributes for principal: {}", getId(), principal);
                return attributes;
            }
        }

        return null;
    }

    /**
     * Stores the attributes in the cache
     *
     * @param resolutionContext <code>ResolutionContext</code>
     * @param query the query used to get the search result
     * @param resolvedAttributes <code>Map</code> of attribute ids to attribute
     */
    protected void setAttributeCache(ShibbolethResolutionContext resolutionContext, String query,
            Map<String, BaseAttribute> resolvedAttributes) {
        String principal = resolutionContext.getAttributeRequestContext().getPrincipalName();
        Map<String, Map<String, BaseAttribute>> cache = dataCache.get(principal);
        if (cache == null) {
            cache = new HashMap<String, Map<String, BaseAttribute>>();
            dataCache.put(principal, cache);
        }
        cache.put(query, resolvedAttributes);
    }

    /** {@inheritDoc} */
    public void validate() throws AttributeResolutionException {
        log.debug("Validating data connector {} configuration.", getId());
        Mongo connection = null;
        try {
            connection = new Mongo(mongoHost);
            if (connection == null) {
                log.error("Unable to create connections using {} data connector ", getId());
                throw new AttributeResolutionException(
                        "Unable to create connections using " + getId() + " data connector.");
            }
        } catch (MongoException e) {
            log.error("Unable to validate {} data connector", getId(), e);
            throw new AttributeResolutionException("Unable to validate " + getId() + " data connector: ", e);
        } finally {
            connection.close();
        }
    }

    /** {@inheritDoc} */
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        if (applicationEvent instanceof LogoutEvent) {
            LogoutEvent logoutEvent = (LogoutEvent) applicationEvent;
            if (cacheResults) {
                dataCache.remove(logoutEvent.getUserSession().getPrincipalName());
            }
        }
    }

    /**
     * Retrieve attributes from the database based on the query
     *
     * @param q the query to run
     * @return a list of attributes
     * @throws AttributeResolutionException if an error occurs during query execution
     */
    protected Map<String, BaseAttribute> retrieveAttributesFromDatabase(String q)
            throws AttributeResolutionException {
        Map<String, BaseAttribute> resolvedAttributes = new HashMap<String, BaseAttribute>();
        try {
            log.debug("Data connector {} retrieving attributes from: {}", getId(), db.getMongo().getAddress());
            DBCollection collection = db.getCollection(mongoCollection);
            DBObject query = (DBObject) JSON.parse(q);
            resolvedAttributes = processCollectionResult(collection.findOne(query));

        } catch (MongoException e) {
            log.error("Data connector {} exception", getId(), e);
            throw new AttributeResolutionException("MongoDB data connector " + getId() + " unable to execute query",
                    e);
        }

        return resolvedAttributes;
    }

    /**
     * Retrieve persistent id attribute for the current principal
     *
     * @param resolutionContext the current resolutionContext
     * @return persistent id attribute
     * @throws AttributeResolutionException if an error occurs when generating or fetching a persistent ID
     */
    protected Map<String, BaseAttribute> retrievePersistentIdAttribute(
            ShibbolethResolutionContext resolutionContext) throws AttributeResolutionException {
        Map<String, BaseAttribute> resolvedAttributes = new HashMap<String, BaseAttribute>();
        String persistentId = getPersistentId(resolutionContext);

        if (persistentId != null) {
            log.debug("Data connector {} got persistentId {} from backend.", getId(), persistentId);
            BasicAttribute<String> attribute = new BasicAttribute<String>();
            attribute.setId(getPidGeneratedAttributeId());
            attribute.getValues().add(persistentId);
            resolvedAttributes.put(attribute.getId(), attribute);
        }

        return resolvedAttributes;
    }

    /**
     * Process the result from the query and return a list of resolved attributes
     *
     * @param result the result from the query
     * @return a list of resolved attributes
     * @throws AttributeResolutionException if an error occurs when reading the mongo db result
     */
    protected Map<String, BaseAttribute> processCollectionResult(DBObject result)
            throws AttributeResolutionException {
        Map<String, BaseAttribute> attributes = new HashMap<String, BaseAttribute>();
        try {
            MongoDbKeyAttributeMapper keyAttributeMapper;
            BaseAttribute attribute;

            if (result != null) {
                for (String keyName : result.keySet()) {
                    log.debug("Processing mongodb key: {} class: {}", keyName, result.get(keyName).getClass());

                    List<MongoDbKeyAttributeMapper> keyChildMap = null;
                    keyAttributeMapper = keyAttributeMap.get(keyName);
                    attribute = getAttribute(attributes, keyAttributeMapper, keyName);
                    if (keyAttributeMapper != null) {
                        keyChildMap = keyAttributeMapper.getChildKeyAttributeMaps();
                    }

                    if (keyChildMap != null && keyChildMap.size() > 0) {
                        BasicDBObject dataMap = (BasicDBObject) result.get(keyName);
                        for (MongoDbKeyAttributeMapper map : keyChildMap) {
                            attribute = getAttribute(attributes, map, map.getAttributeName());
                            attribute.getValues().add(dataMap.get(map.getMongoKey()));
                            attributes.put(attribute.getId(), attribute);
                        }
                    } else if (result.get(keyName) instanceof com.mongodb.BasicDBList) {
                        log.debug("Processing BasicDBList for {}.", keyName);
                        BasicDBList res = (BasicDBList) result.get(keyName);
                        List<String> resultList = new ArrayList<String>();
                        for (Object s : res) {
                            if (s instanceof BasicDBObject) {
                                log.error("BasicDBObjects in embedded lists not supported");
                                continue;
                            }
                            resultList.add((String) s);
                        }
                        attribute.getValues().addAll(resultList);
                        attributes.put(attribute.getId(), attribute);
                    } else {
                        attribute.getValues().add(result.get(keyName));
                        attributes.put(attribute.getId(), attribute);
                    }
                }
            }
        } catch (MongoException e) {
            log.error("Problem processing result {}:", getId(), e);
            throw new AttributeResolutionException("Problem processing result " + getId() + ":", e);
        }
        log.debug("MongoDb data connector {} attribute result: {}", getId(), attributes.keySet());
        return attributes;
    }

    /**
     * Gets a base attribute object.
     *
     * @param attributes the attribute list
     * @param keyAttributeMapper the mongo db key attribute mapper
     * @param keyName the attribute name
     * @return base attribute
     */
    private BaseAttribute getAttribute(Map<String, BaseAttribute> attributes,
            MongoDbKeyAttributeMapper keyAttributeMapper, String keyName) {
        BaseAttribute attribute;

        if (keyAttributeMapper == null || keyAttributeMapper.getAttributeName() == null) {
            attribute = attributes.get(keyName);
            if (attribute == null) {
                attribute = new BasicAttribute(keyName);
            }
        } else {
            attribute = attributes.get(keyAttributeMapper.getAttributeName());
            if (attribute == null) {
                attribute = new BasicAttribute(keyAttributeMapper.getAttributeName());
            }
        }

        return attribute;
    }

    /**
     * Derived from shibboleth storedId, Copyright [2007] [University Corporation for Advanced Internet Development, Inc.].
     *
     * Gets the currently active identifier for a (principal, peer, local) tuple.
     *
     * @param resolutionContext current resolution context
     * @return persistent ID
     * @throws AttributeResolutionException if an error occurs when saving object to the database
     */
    private String getPersistentId(ShibbolethResolutionContext resolutionContext)
            throws AttributeResolutionException {
        SAMLProfileRequestContext requestContext = resolutionContext.getAttributeRequestContext();
        ArrayList<PersistentIdEntry> entries = new ArrayList<PersistentIdEntry>();
        String localId = getLocalId(resolutionContext);

        try {
            DBCollection collection = db.getCollection(mongoCollection);
            BasicDBObject query = new BasicDBObject();
            query.put("localEntity", requestContext.getLocalEntityId());
            query.put("peerEntity", requestContext.getInboundMessageIssuer());
            query.put("localId", localId);
            query.put("deactivationTime", new BasicDBObject("$exists", false));

            log.debug("Data connector {} trying to fetch persistentId for principal: {}", getId(), localId);

            DBCursor result = collection.find(query);

            PersistentIdEntry entry;
            while (result.hasNext()) {
                DBObject r = result.next();
                entry = new PersistentIdEntry();
                entry.setLocalEntityId((String) r.get("localEntity"));
                entry.setPeerEntityId((String) r.get("peerEntity"));
                entry.setPrincipalName((String) r.get("principalName"));
                entry.setPersistentId((String) r.get("persistentId"));
                entry.setLocalId((String) r.get("localId"));
                entry.setCreationTime((Date) r.get("creationTime"));
                entry.setDeactivationTime((Date) r.get("deactivationTime"));
                entry.setLastVisitTime(new Date());

                // Update last visit time
                BasicDBObject updateQuery = new BasicDBObject("$set",
                        new BasicDBObject("lastVisitTime", entry.getLastVisitTime()));
                collection.update(r, updateQuery);

                entries.add(entry);
            }

            if (entries == null || entries.size() == 0) {
                log.debug("Data connector {} did not find a persistentId for principal: {}, generating a new one.",
                        getId(), localId);
                entry = createPersistentId(resolutionContext, localId);
                savePersistentId(entry);
                entries.add(entry);
            }
        } catch (MongoException e) {
            log.error("MongoDB query failed", e);
        }

        return entries.get(0).getPersistentId();
    }

    /**
     * Creates a persistent ID that is unique for a given local/peer/localId tuple.
     *
     * A random type 4 UUID is generated as the persistent ID.
     *
     * @param resolutionContext current resolution context
     * @param localId principal the persistent ID represents
     * @return persistent ID entry
     */
    protected PersistentIdEntry createPersistentId(ShibbolethResolutionContext resolutionContext, String localId) {
        PersistentIdEntry persistentIdEntry = new PersistentIdEntry();
        persistentIdEntry.setPeerEntityId(resolutionContext.getAttributeRequestContext().getPeerEntityId());
        persistentIdEntry.setLocalEntityId(resolutionContext.getAttributeRequestContext().getLocalEntityId());
        persistentIdEntry.setPrincipalName(resolutionContext.getAttributeRequestContext().getPrincipalName());
        persistentIdEntry.setLocalId(localId);
        persistentIdEntry.setPersistentId(UUID.randomUUID().toString());
        persistentIdEntry.setCreationTime(new Date());
        persistentIdEntry.setLastVisitTime(new Date());

        return persistentIdEntry;
    }

    /**
     * Save a persistent ID entry into the mongo database.
     *
     * @param entry entry to persist
     */
    protected void savePersistentId(PersistentIdEntry entry) {
        try {
            DBCollection collection = db.getCollection(mongoCollection);
            BasicDBObject query = new BasicDBObject();
            query.put("localEntity", entry.getLocalEntityId());
            query.put("peerEntity", entry.getPeerEntityId());
            query.put("principalName", entry.getPrincipalName());
            query.put("localId", entry.getLocalId());
            query.put("persistentId", entry.getPersistentId());
            query.put("creationTime", entry.getCreationTime());
            query.put("lastVisitTime", entry.getLastVisitTime());

            collection.insert(query);

        } catch (MongoException e) {
            log.error("Failed to save persistent ID to the database", e);
        }
    }

    /**
     * Derived from shibboleth storedId, Copyright [2007] [University Corporation for Advanced Internet Development, Inc.].
     *
     * Gets the local ID component of the persistent ID.
     *
     * @param resolutionContext current resolution context
     * @return local ID component of the persistent ID
     * @throws AttributeResolutionException if there is a problem resolving the local id
     */
    protected String getLocalId(ShibbolethResolutionContext resolutionContext) throws AttributeResolutionException {
        Collection<Object> sourceIdValues = getValuesFromAllDependencies(resolutionContext,
                getPidSourceAttributeId());
        if (sourceIdValues == null || sourceIdValues.isEmpty()) {
            log.error("Source attribute {} for connector {} provide no values", getPidSourceAttributeId(), getId());
            throw new AttributeResolutionException("Source attribute " + getPidSourceAttributeId()
                    + " for connector " + getId() + " provided no values");
        }

        if (sourceIdValues.size() > 1) {
            log.warn("Source attribute {} for connector {} has more than one value, only the first value is used",
                    getPidSourceAttributeId(), getId());
        }

        return sourceIdValues.iterator().next().toString();
    }

    /**
     * Gets the number of persistent ID entries for a (principal, peer, local) tuple.
     *
     * @param localEntity entity ID of the ID issuer
     * @param peerEntity entity ID of the peer the ID is for
     * @param localId ID part of the persistent ID
     * @return number of persistent ID entries for a (principal, peer, local) tuple
     */
    public int getNumberOfPersistentIdEntries(String localEntity, String peerEntity, String localId) {
        try {
            DBCollection collection = db.getCollection(mongoCollection);

            log.debug("Get number of persistent ids for connector {}", getId());
            BasicDBObject query = new BasicDBObject();
            query.put("localEntity", localEntity);
            query.put("peerEntity", peerEntity);
            query.put("localId", localId);

            return collection.find(query).count();

        } catch (MongoException e) {
            log.error("Mongodb query failed", e);
        }
        return 0;
    }

    /**
     * Gets the engine used to build the query.
     *
     * @return engine used to build the query
     */
    public TemplateEngine getTemplateEngine() {
        return queryCreator;
    }

    /**
     * Sets the engine used to build the query.
     *
     * @param engine used to build the query
     */
    public void setTemplateEngine(TemplateEngine engine) {
        queryCreator = engine;
        registerTemplate();
        resetCache();
    }

    /**
     * Gets the mongo database username.
     *
     * @return mongo database username
     */
    public String getMongoUser() {
        return mongoUser;
    }

    /**
     * Sets the mongo database username.
     *
     * @param user the mongo database username
     */
    public void setMongoUser(String user) {
        mongoUser = user;
    }

    /**
     * Gets the mongo database password.
     *
     * @return mongo database password
     */
    public String getMongoPassword() {
        return mongoPassword;
    }

    /**
     * Sets the mongo database password.
     *
     * @param password the mongo database password
     */
    public void setMongoPassword(String password) {
        mongoPassword = password;
    }

    /**
     * Gets preferred read method for MongoDB.
     * Defaults to primaryPreferred.
     *
     * @return a ReadPreference
     */
    public ReadPreference getPreferredRead() {
        if (preferredRead != null && !preferredRead.isEmpty()) {
            return MONGO_READ_PREF.get(preferredRead);
        }
        return ReadPreference.primaryPreferred();
    }

    /**
     * Sets the preferred read method for MongoDB.
     *
     * @param prefRead the preferred read keyword
     */
    public void setPreferredRead(String prefRead) {
        if (MONGO_READ_PREF.containsKey(prefRead)) {
            preferredRead = prefRead;
        } else {
            throw new IllegalArgumentException(
                    "Invalid value '" + prefRead + "'. Valid values are: " + MONGO_READ_PREF.keySet().toString());
        }
    }

    /**
     * Gets whether to cache search results.
     *
     * @return cacheResults whether to cache query result
     */
    public boolean isCacheResults() {
        return cacheResults;
    }

    /**
     * Sets whether to cache search results.
     *
     * @param cache whether to cache search results
     */
    public void setCacheResults(boolean cache) {
        cacheResults = cache;
    }

    /**
     * Gets the template used to create a query.
     *
     * @return template used to createa a query
     */
    public String getQueryTemplate() {
        return queryTemplate;
    }

    /**
     * Sets the template used to create a query.
     *
     * @param template used to create a query
     */
    public void setQueryTemplate(String template) {
        queryTemplate = template;
        resetCache();
    }

    /**
     * Gets a list of key and attribute mappings.
     *
     * @return list of key and attribute mappings
     */
    public Map<String, MongoDbKeyAttributeMapper> getKeyAttributeMap() {
        return keyAttributeMap;
    }

    /**
     * Sets a map of key and attribute mappings.
     *
     * @param map of key and attribute mappings
     */
    public void setKeyAttributeMap(Map<String, MongoDbKeyAttributeMapper> map) {
        keyAttributeMap = map;
    }

    /**
     * Gets whether persistent id mode is enabled.
     *
     * @return whether persistent id mode is enabled
     */
    public boolean isPersistentId() {
        return usePersistentId;
    }

    /**
     * Sets whether to enable persistent id mode.
     *
     * @param persistentId whether to enable persistent id mode
     */
    public void setPersistentId(boolean persistentId) {
        usePersistentId = persistentId;
    }

    /**
     * Gets the ID of the attribute generated by this data connector.
     *
     * @return ID of the attribute generated by this data connector
     */
    public String getPidGeneratedAttributeId() {
        return pidGeneratedAttributeId;
    }

    /**
     * Sets the ID of the attribute generated by this data connector.
     *
     * @param attributeId the ID of the attribute generated by this data connector
     */
    public void setPidGeneratedAttributeId(String attributeId) {
        pidGeneratedAttributeId = attributeId;
    }

    /**
     * Gets the ID of the attribute whose first value  is used when generating a persistent ID.
     *
     * @return the ID of the attribute whose first value  is used when generating a persistent ID
     */
    public String getPidSourceAttributeId() {
        return pidSourceAttributeId;
    }

    /**
     * Sets the ID of the attribute whose first value  is used when generating a persistent ID.
     *
     * @param attributeId the ID of the attribute whose first value  is used when generating a persistent ID
     */
    public void setPidSourceAttributeId(String attributeId) {
        pidSourceAttributeId = attributeId;
    }
}