C H A P T E R  6

User Profile API

This chapter describes the Sun Java System Content Delivery Server User Management API. Use this API to create a subscriber adapter to add, delete, retrieve, update, enable, and disable users. This API also provides methods for retrieving information about devices from the Content Delivery Server database.

The User Profile API consists of the following classes and interfaces:

For additional information on classes and methods, see the HTML output of the Javadoc tool for the User Profile API at $CDS_HOME/javadoc/cdsapi/index.html.


6.1 UserManager Class

The UserManager class defines methods to create, delete, or access information for a user. Extend this class and implement all abstract methods as needed to interact with your subscriber database. To get information about the devices known to Content Delivery Server, you can use the getDeviceModel and getDeviceModelList methods.

For information on methods not described, see the HTML output of the Javadoc tool for the User Profile API at $CDS_HOME/javadoc/cdsapi/index.html.

6.1.1 doFormatMobileId Method

protected abstract String doFormatMobileId(String mobileId); 

The doFormatMobileId method formats the mobile ID to match the requirements of your subscriber database. For example, if your database cannot handle hyphens in the mobile ID and the subscriber enters a mobile ID that contains hyphens when registering with Content Delivery Server, you can implement this method to remove the special characters.

6.1.2 doFormatLoginId Method

protected abstract String doFormatLoginId(String loginId); 

The doFormatLoginID method formats the login ID to match the requirements of your subscriber database. For example, if your database requires that all login IDs be lowercase and a subscriber enters an ID with uppercase letters when registering with Content Delivery Server, you can implement this method to convert the ID to lowercase.

6.1.3 doGetUserDeviceModel Method

protected long doGetUserDeviceModel(User user, UserDeviceModel model);

The doGetUserDeviceModel method identifies the device that is associated with a subscriber. Content Delivery Server calls this method when a subscriber logs in or when a subscriber chooses to give a gift to or share content with someone who is not registered with Content Delivery Server.

You must return one of the following values:

Return the handset ID if you have enough information to identify the subscriber’s device. This ID is the ID that was assigned by Content Delivery Server when the device was added to the system. If you know the user agent pattern for the device, you can call the UserManager.getDeviceModel method and get the handset ID from the object returned. To get a list of devices known to Content Delivery Server, call the UserManager.getDeviceModelList method. You can search this list by name, model number, or other attribute to identify the device in which you are interested and get the handset ID.If you return the ID of a device that is different than the device model that was passed in, the user profile for the subscriber is updated with the new device.

Return this value if the device cannot be determined or you want the existing device, if any, to be used.

Return this value if the device cannot be determined or if you want to remove the association of the device with the subscriber.


6.2 User Interface

The User interface defines common methods to get, set, or delete user attributes. Implement this interface and all methods. For additional information, see the HTML output of the Javadoc tool for the User Profile API at $CDS_HOME/javadoc/cdsapi/index.html.


6.3 UserDeviceManager Interface

The UserDeviceManager interface defines a method for accessing the unique ID for a device such as the ESN (Electronic Serial Number) given the IP address that the device is using to connect. Implement this interface and its method. For additional information, see the HTML output of the Javadoc tool for the User Profile API at $CDS_HOME/javadoc/cdsapi/index.html.


6.4 UserDeviceModel Interface

The UserDeviceModel interface defines methods for accessing information about a device that is known to Content Delivery Server. Objects of this type are returned by methods you might call. For additional information, see the HTML output of the Javadoc tool for the User Profile API at $CDS_HOME/javadoc/cdsapi/index.html.


6.5 Using the User Profile API

The classes for the User Profile API are available in cdsapi.jar. This file must be in your classpath when you compile your adapter. For convenience, a copy of all Content Delivery Server JAR files are available in the $CDS_HOME/dist/cds/staging/jar directory. Use this staging area in your classpath when compiling the adapter that you create.

Making your adapter available to Content Delivery Server depends on the application server you are using and whether you have already deployed. To make your adapter available, follow these steps:

1. Create a JAR file for your adapter.

2. For all application servers, place the JAR file in the $CDS_HOME/dist/cds/lib/external directory.

The adapter is now included in all future deployments.

3. If you have existing deployments that need to use the adapter, place the JAR file in the $CDS_HOME/deployment/deployment-name/lib/external directory for each deployment.

If you are using WebLogic Server, the classpath is handled for you.

If you are using Sun Java System Application Server, update the classpath for each deployment:

a. Back up the $CDS_HOME/deployment/deployment-name/sun/domains/cdsdomain/config/domain.xml file before editing it so you can recover from any errors that might be introduced during editing.

b. Edit domain.xml and modify the java-config element to add the absolute path for your JAR file to the classpath-suffix attribute.

c. Save your changes.

4. Edit the security.config file in the $CDS_HOME/deployment/deployment-name/conf directory:

a. Set the module.security.subscriber.usermanager property to the class name of your implementation of the User interface, for example:

module.security.subscriber.usermanager=com.sun.content.server.vending.security.user.SubscriberImpl

The property name and value must be on a single line. The sample statement spans two lines to fit on the page.

b. Save your changes.

5. Restart any existing deployment to make it aware of the new JAR file.


6.6 Sample Implementation of the User Manager API

The code samples in this section provide an example of a subscriber adapter that was created using the User Profile API. This implementation indexes and stores user information on a local file system as subscriber accounts are created in Content Delivery Server.

6.6.1 SampleUserImpl.java

The following code is a sample implementation of the User interface. This interface includes the fields that Content Delivery Server uses for user profiles. If you have additional fields for your specific implementation, add the methods required to get and set those values.


CODE EXAMPLE 6-1 Sample User Implementation
package com.wireless.adapter;
 
import com.sun.content.server.service.security.User;
import java.util.Date;
import java.util.Hashtable;
import java.io.Serializable;
 
public class SampleUserImpl implements User, Serializable {
 
    long uid = -1L;
 
    private String loginId;
    private String firstName;
    private String lastName;
    private String email;
    private String middleName;
    private String gender;
    private String street1;
    private String street2;
    private String postalCode;
    private String city;
    private String state;
    private String country;
    private String phone;
    private String salutation;
    private boolean enabled;
    String password;
    private String uniqueDeviceId;
    private String mobileId;
    private boolean prepay;
 
    public SampleUserImpl() {
    }
 
    public String getLoginId() { return loginId; }
    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public String getEmail() { return email; }
    public void setLoginId(String loginId) { this.loginId = loginId; }
    public void setFirstName(String firstName) { this.firstName = firstName; }
    public void setLastName(String lastName) { this.lastName = lastName; }
    public void setEmail(String email) { this.email = email; }
    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }
    public String getMiddleName() { return middleName; }
    public void setGender(String gender) { this.gender = gender; }
    public String getGender() { return gender; }
    public String getStreet1() { return street1; }
    public void setStreet1(String street1) { this.street1 = street1; }
    public String getStreet2() { return street2; }
    public void setStreet2(String street2) { this.street2 = street2; }
    public String getPostalCode() { return postalCode; }
    public void setPostalCode(String postalCode) {
        this.postalCode = postalCode;
    }
    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
    public String getState() { return state; }
    public void setState(String state) { this.state = state; }
    public String getCountry() { return country; }
    public void setCountry(String country) { this.country = country; }
    public String getPhone() { return phone; }
    public void setPhone(String phone) { this.phone = phone; }
    public String getSalutation() { return salutation; }
    public void setSalutation(String salutation) {
        this.salutation = salutation;
    }
    public boolean isEnabled() { return enabled; }
    public void setIsEnabled(boolean enabled) { this.enabled = enabled; }
    public String getPassword() {
        // we will not disclose password !
        return null;
    }
    public void setPassword(String password) { this.password = password; }
    public String getUniqueDeviceId() { return uniqueDeviceId; }
    public void setUniqueDeviceId(String uniqueDeviceId) {
        this.uniqueDeviceId = uniqueDeviceId;
    }
    public String getMobileId() { return mobileId; }
    public void setMobileId(String mobileId) { this.mobileId = mobileId; }
    public boolean isPrepay() { return prepay; }
    public void setIsPrepay(boolean prepay) { this.prepay = prepay; }
 
    // deprecated methods
    public Date getCreateDate() { return null; }
    public void setCreateDate(Date date) {}
    public Date getLastLogin() { return null; }
    public void setLastLogin(Date date) {}
    public Object getAttribute(String name) { return null; }
    public Object getAttribute(String name, Object def) { return null; }
    public Hashtable getAttributes() { return null; }
    public void setHasLoggedIn(boolean b) {}
    public boolean hasLoggedIn() { return false; }
    public void setAttribute(String name, Object val) {}
    public void setAttributes(Hashtable h) {}
    public boolean isConfirmed() { return false; }
    public void updateLastLogin() {}
    public Date getActivateDate() { return null; }
    public void setActivateDate(Date date) {}
    public Date getDeActivateDate() { return null; }
    public void setDeActivateDate(Date date) {}
 
}

6.6.2 SampleUserManagerImpl.java

The following code is a sample extension of the UserManager class.


CODE EXAMPLE 6-2 Sample UserManager Implementation
package com.wireless.adapter;
 
import com.sun.content.server.service.security.*;
import com.sun.content.server.foundation.log.LogCategory;
import com.sun.content.server.service.security.util.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.atomic.*;
 
/**
 * A sample user manager for Content Delivery Server.
 *
 * <p>
 * The sample user manager indexes and stores user information on a
 * local file system. The implementation is rudimentary, and doesn't support
 * concurrent file modification of the database, so only one server should
 * be sharing the user data.
 *
 * <p>
 * The user manager is provided strictly for educational and experimental
 * purposes, it has not passed any testing.
 */
public class SampleUserManagerImpl extends UserManager {
 
    // you can change these constants
    private final static String FS_ROOT = "/var/tmp/cds_user_db";
    private final static String FS_INDEX_LOGIN = "login.idx";
    private final static String FS_INDEX_UNIQUE = "unique.idx";
    private final static String FS_INDEX_MOBILE = "mobile.idx";
    private final static String FS_INDEX = "master.idx";
    private final static String DS_KEY = "____sum___timestamp";
    private final static long LDS_KEY = -1L;
    private final static String EXT = ".act";
 
    private static Map<String,Long> indexLogin;
    private static Map<String,Long> indexUnique;
    private static Map<String,Long> indexMobile;
    private static Map<Long,String[]> index;
    private static Object idxLock = new Object();
 
    private static AtomicLong seq = new AtomicLong();
 
    private static boolean dirty;
    private static Object dirtyLock = new Object();
 
    private static LogCategory log = LogCategory.getLog();
 
    static {
        init();
    }
 
    private static void init() {
 
        File fs = new File(FS_ROOT);
 
        if (!fs.exists()) {
            if (!fs.mkdirs()) {
                throw new RuntimeException("Can not create directories"+
                        " for user database " + fs.getAbsolutePath());
            }
        }
 
        File f = new File(fs, FS_INDEX_LOGIN);
        indexLogin = readMap(f);
        f = new File(fs, FS_INDEX_UNIQUE);
        indexUnique = readMap(f);
        f = new File(fs, FS_INDEX_MOBILE);
        indexMobile = readMap(f);
        f = new File(fs, FS_INDEX);
        index = readMap(f);
 
        if (index == null) {
            rebuildMasterIndex();
        }
 
        long masterStamp = Long.parseLong(index.get(LDS_KEY)[0]);
        if (indexLogin != null && indexLogin.get(DS_KEY) < masterStamp) {
            indexLogin = null;
        }
        if (indexUnique != null && indexUnique.get(DS_KEY) < masterStamp) {
            indexUnique = null;
        }
        if (indexMobile != null && indexMobile.get(DS_KEY) < masterStamp) {
            indexMobile = null;
        }
 
        boolean needIL = false;
        boolean needIU = false;
        boolean needIM = false;
 
        if (indexLogin == null) {
            log.info("SUM: will rebuild login index");
            needIL = true;
            indexLogin = new HashMap<String,Long>();
        }
        if (indexUnique == null) {
            log.info("SUM: will rebuild unique index");
            needIU = true;
            indexUnique = new HashMap<String,Long>();
        }
        if (indexMobile == null) {
            log.info("SUM: will rebuild mobile index");
            needIM = true;
            indexMobile = new HashMap<String,Long>();
        }
 
        long maxVal = 0L;
 
        for (Map.Entry<Long,String[]> entry : index.entrySet()) {
 
            Long key = entry.getKey();
 
            if (key.equals(LDS_KEY)) { continue; }
 
            String [] data = entry.getValue();
 
            if (needIL) {
                indexLogin.put(data[1], key);
            }
            if (needIU) {
                indexUnique.put(data[2], key);
            }
            if (needIM) {
                indexMobile.put(data[3], key);
            }
 
            if (maxVal < key) { maxVal = key; }
        }
 
        seq.set(maxVal);
 
        log.info("SUM: initialized, index is "+ index.size()+" entries");
 
        dirty = true;
 
        Runnable r = new Updater();
        (new Thread(r)).start();
 
    }
 
    private static void rebuildMasterIndex() {
 
        try {
            log.info("SUM: rebuilding master index");
            File fs = new File(FS_ROOT);
 
            index = new HashMap<Long,String[]>();
 
            File [] lst = fs.listFiles(new FilenameFilter() {
                public boolean accept(File dir, String name) {
                    return name.endsWith(EXT);
                } });
 
            for (File file : lst) {
                SampleUserImpl user = readUser(file, false);
                if (user == null) { continue; }
                if (seq.get() <= user.uid) { seq.set(user.uid + 1L); }
                index.put(user.uid, new String[]{
                        file.getName(),
                        user.getLoginId(),
                        user.getUniqueDeviceId(),
                        user.getMobileId()});
            }
 
            index.put(LDS_KEY,
                    new String[]{String.valueOf(System.currentTimeMillis())});
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
 
    private static SampleUserImpl readUser(File f,
            boolean reThrow) throws Exception {
 
        try {
            FileInputStream fis = new FileInputStream(f);
            ObjectInputStream ois = new ObjectInputStream(fis);
            SampleUserImpl user = (SampleUserImpl)ois.readObject();
            ois.close();
            fis.close();
            return user;
        } catch (Exception e) {
            if (reThrow) { throw e; }
            log.warn("error reading file "+f.getAbsolutePath(), e);
        }
        return null;
 
    }
 
    private static Map readMap(File f) {
        try {
 
            FileInputStream fis = new FileInputStream(f);
            ObjectInputStream ois = new ObjectInputStream(fis);
            Map map = (Map)ois.readObject();
            ois.close();
            fis.close();
            return map;
 
        } catch (Exception e) {
            log.warn("error reading file "+f.getAbsolutePath(), e);
        }
        return null;
    }
 
    public SampleUserManagerImpl() {
    }
 
    public boolean doAddUser(User _user) throws UserProfileResourceException {
        SampleUserImpl user = (SampleUserImpl)_user;
 
        synchronized (user) {
 
            if (user.uid != -1L) {
                throw new UserProfileResourceException("the user is already"+
                        " added");
            }
 
            user.uid = seq.incrementAndGet();
 
            doUpdateUser(_user);
        }
 
        return true;
    }
 
    protected User doGetUserInstance() {
        return new SampleUserImpl();
    }
 
    protected User doGetUserByUniqueDeviceId(String id)
            throws UserProfileResourceException {
        return getUserFromIndex(id, indexUnique);
    }
 
    protected User doGetUserByMobileId(String id)
            throws UserProfileResourceException {
        return getUserFromIndex(id, indexMobile);
    }
 
    protected User doGetUserByLoginId(String id)
            throws UserProfileResourceException {
        return getUserFromIndex(id, indexLogin);
    }
 
    protected boolean doAuthenticatePassword(User _user, String pwd) {
        return pwd.equals(((SampleUserImpl)_user).password);
    }
 
    protected void doEnableUser(User _user)
            throws UserProfileResourceException {
 
        SampleUserImpl user = (SampleUserImpl)_user;
        if (user.isEnabled()) { return; }
        user.setIsEnabled(true);
        doUpdateUser(_user);
    }
 
    protected void doDisableUser(User _user)
            throws UserProfileResourceException {
 
        SampleUserImpl user = (SampleUserImpl)_user;
        if (!user.isEnabled()) { return; }
        user.setIsEnabled(false);
        doUpdateUser(_user);
    }
 
    protected boolean doRemoveUser(User _user)
            throws UserProfileResourceException {
 
        synchronized (idxLock) {
            SampleUserImpl user = (SampleUserImpl)_user;
            String [] data = index.get(user.uid);
            if (data == null) { return false; }
            index.remove(user.uid);
            indexLogin.remove(data[1]);
            indexUnique.remove(data[2]);
            indexMobile.remove(data[3]);
            File f = new File(FS_ROOT, data[0]);
            f.delete();
            user.uid = -1L;
        }
        synchronized (dirtyLock) {
            dirty = true;
        }
 
        return true;
    }
 
    protected boolean doUpdateUser(User _user)
            throws UserProfileResourceException {
 
        SampleUserImpl user = (SampleUserImpl)_user;
 
        if (user.uid == -1L) { return false; }
 
        try {
 
            File fs = new File(FS_ROOT);
 
            String [] data = index.get(user.uid);
            if (data == null) {
                File tc = File.createTempFile("sum-", EXT, fs);
                data = new String[]{
                    tc.getName(), user.getLoginId(),
                    user.getUniqueDeviceId(), user.getMobileId()
                };
            }
 
            // dump out user data
 
            File f = new File(fs, data[0]);
            FileOutputStream fos = new FileOutputStream(f);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(user);
            oos.close();
            fos.close();
 
            synchronized (idxLock) {
                index.put(user.uid, data);
                indexLogin.remove(data[1]);
                indexLogin.put(user.getLoginId(), user.uid);
                indexUnique.remove(data[2]);
                indexUnique.put(user.getUniqueDeviceId(), user.uid);
                indexMobile.remove(data[3]);
                indexMobile.put(user.getMobileId(), user.uid);
            }
 
            synchronized (dirtyLock) {
                dirty = true;
            }
 
 
        } catch (Exception e) {
            e.printStackTrace();
            throw new UserProfileResourceException(e);
        }
 
        return true;
 
    }
 
    private User getUserFromIndex(String id,
            Map<String,Long> index) throws UserProfileResourceException {
 
        try {
            Long pk = index.get(id);
            if (pk == null) { return null; }
            return getUserByPK(pk);
        } catch (Exception e) {
            e.printStackTrace();
            throw new UserProfileResourceException(e);
        }
    }
 
    private User getUserByPK(Long pk) throws Exception {
        String [] data = index.get(pk);
        if (data == null) { return null; }
        File f = new File(FS_ROOT, data[0]);
        return readUser(f, true);
    }
 
    protected String doFormatMobileId(String id) {
        return id.trim();
    }
 
    protected String doFormatLoginId(String id) {
        return id.trim();
    }
 
    static class Updater implements Runnable {
 
        public void run() {
 
            Thread.currentThread().setName("SUM-Updater");
 
            log.info("SUM: updater thread initialized");
 
            while (true) {
 
                try {
                    Thread.sleep(2000);
                } catch (Exception e) {}
 
                synchronized (dirtyLock) {
                    if (!dirty) { continue; }
                    dirty = false;
                }
                // the maps are dirty, well...
 
                try {
 
                    long stamp = System.currentTimeMillis();
 
                    Map<Long,String[]> _index = new HashMap<Long,String[]>();
                    Map<String,Long> _login = new HashMap<String,Long>();
                    Map<String,Long> _unique = new HashMap<String,Long>();
                    Map<String,Long> _mobile = new HashMap<String,Long>();
 
                    synchronized (idxLock) {
                        index.put(LDS_KEY, new String[]{String.valueOf(stamp)});
                        indexLogin.put(DS_KEY, stamp);
                        indexUnique.put(DS_KEY, stamp);
                        indexMobile.put(DS_KEY, stamp);
 
                        _index.putAll(index);
                        _login.putAll(indexLogin);
                        _unique.putAll(indexUnique);
                        _mobile.putAll(indexMobile);
                    }
 
                    File f = new File(FS_ROOT, FS_INDEX);
                    FileOutputStream fos = new FileOutputStream(f);
                    ObjectOutputStream oos = new ObjectOutputStream(fos);
                    oos.writeObject(_index);
                    oos.close();
                    fos.close();
 
                    f = new File(FS_ROOT, FS_INDEX_LOGIN);
                    fos = new FileOutputStream(f);
                    oos = new ObjectOutputStream(fos);
                    oos.writeObject(_login);
                    oos.close();
                    fos.close();
 
                    f = new File(FS_ROOT, FS_INDEX_UNIQUE);
                    fos = new FileOutputStream(f);
                    oos = new ObjectOutputStream(fos);
                    oos.writeObject(_unique);
                    oos.close();
                    fos.close();
 
                    f = new File(FS_ROOT, FS_INDEX_MOBILE);
                    fos = new FileOutputStream(f);
                    oos = new ObjectOutputStream(fos);
                    oos.writeObject(_mobile);
                    oos.close();
                    fos.close();
 
                    log.debug("SUM: indices synced to fs");
 
                } catch (Exception e) {
                    e.printStackTrace();
                }
 
            }
 
        }
 
    }
 
}

6.6.3 Working With the Sample Adapter

The sample adapter can be run with a test deployment, if you want to experiment with the code. There must be no subscriber accounts in the Vending Manager when you first deploy this adapter.

To work with the sample adapter, follow these steps:

1. Stop the Vending Manager, if it is running.

The subscriber adapter must run on the server on which the Vending Manager is deployed.

2. Create the SampleUserImpl and SampleUserManagerImpl classes in a directory, for example /tmp/sum.

3. Execute the following commands in sequence from the directory that contains the class files you just created.

Each command is entered as a single line of code.

$ mkdir bin
$ javac -d bin -classpath $CDS_HOME/dist/cds/lib/cdslib/cdsapi.jar:$CDS_HOME/dist/cds/lib/cdslib/foundation.jar SampleUserImpl.java SampleUserManagerImpl.java && ( cd bin && jar cf ../sum.jar .)
$ cp sum.jar $CDS_HOME/deployment/deployment-name/lib/external/

4. Edit the $CDS_HOME/deployment/deployment-name/conf/security.config file.

Set the module.security.subscriber.usermanager property to com.wireless.adapter.SampleUserManagerImpl.

5. Start the Vending Manager.