001    /*
002     * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003     * Copyright (C) 2011 NightLabs Consulting GmbH
004     *
005     * This program is free software: you can redistribute it and/or modify
006     * it under the terms of the GNU Affero General Public License as
007     * published by the Free Software Foundation, either version 3 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU Affero General Public License for more details.
014     *
015     * You should have received a copy of the GNU Affero General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    package org.cumulus4j.store.model;
019    
020    import javax.jdo.annotations.Column;
021    import javax.jdo.annotations.IdentityType;
022    import javax.jdo.annotations.NullValue;
023    import javax.jdo.annotations.PersistenceCapable;
024    import javax.jdo.annotations.Persistent;
025    import javax.jdo.annotations.PrimaryKey;
026    
027    import org.cumulus4j.store.Cumulus4jIncrementGenerator;
028    import org.cumulus4j.store.crypto.CryptoContext;
029    
030    /**
031     * Persistent sequence entity used by {@link Cumulus4jIncrementGenerator}.
032     * <p>
033     * Objects are cached by DataNucleus via their primary key. Accessing an object via its OID therefore does not
034     * require any query, if the object is already cached. Therefore, this class encodes the
035     * {@link CryptoContext#getKeyStoreRefID() keyStoreRefID} and the <code>sequenceName</code> together in one
036     * single {@link #getSequenceID() sequenceID}, which is the (single-field) primary key for this class.
037     * <p>
038     * We do not use a composite primary key, because this is not supported by all underlying databases. The chosen
039     * strategy is thus the most portable and fastest.
040     *
041     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
042     * @since 1.1.0
043     */
044    @PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true")
045    public class Sequence2
046    {
047            @PrimaryKey
048            @Persistent(nullValue=NullValue.EXCEPTION)
049            @Column(length=255)
050            private String sequenceID;
051    
052            private long nextValue = 1;
053    
054            /**
055             * Default constructor. Should never be used by actual code! It exists only to fulfill the JDO requirements.
056             */
057            protected Sequence2() { }
058    
059            protected static String createSequenceID(int keyStoreRefID, String sequenceName) {
060                    return Integer.toString(keyStoreRefID) + '.' + sequenceName;
061            }
062    
063            /**
064             * Constructor creating a <code>Sequence</code> with the given primary key.
065             * @param sequenceName the name of the sequence; must not be <code>null</code>.
066             */
067            protected Sequence2(int keyStoreRefID, String sequenceName)
068            {
069                    if (sequenceName == null)
070                            throw new IllegalArgumentException("sequenceName == null");
071    
072                    this.sequenceID = createSequenceID(keyStoreRefID, sequenceName);
073            }
074    
075            public String getSequenceID() {
076                    return sequenceID;
077            }
078    
079            protected String[] splitSequenceID() {
080                    int dotIndex = sequenceID.indexOf('.');
081                    if (dotIndex < 0)
082                            throw new IllegalStateException(String.format("sequenceID \"%s\" does not contain a dot ('.')!", sequenceID));
083    
084                    String[] result = new String[2];
085                    result[0] = sequenceID.substring(0, dotIndex);
086                    result[1] = sequenceID.substring(dotIndex + 1);
087                    return result;
088            }
089    
090            public int getKeyStoreRefID() {
091                    String keyStoreRefIDStr = splitSequenceID()[0];
092                    try {
093                            int keyStoreRefID = Integer.parseInt(keyStoreRefIDStr);
094                            return keyStoreRefID;
095                    } catch (NumberFormatException x) {
096                            throw new IllegalStateException(
097                                            String.format(
098                                                            "First part of sequenceID \"%s\" is \"%s\", which is not a valid integer: %s",
099                                                            sequenceID, keyStoreRefIDStr, x.toString()
100                                            ),
101                                            x
102                            );
103                    }
104            }
105    
106            /**
107             * Get the name of the sequence.
108             * @return the name of the sequence.
109             */
110            public String getSequenceName() {
111                    return splitSequenceID()[1];
112            }
113    
114            /**
115             * Get the next value (i.e. the first unused value) for this sequence.
116             * @return the next value (i.e. the first unused value) for this sequence.
117             */
118            public long getNextValue() {
119                    return nextValue;
120            }
121    
122            /**
123             * Set the next value (i.e. the first unused value) for this sequence.
124             * @param nextValue the next value (i.e. the first unused value) for this sequence.
125             */
126            public void setNextValue(long nextValue) {
127                    this.nextValue = nextValue;
128            }
129    }