001package org.biopax.paxtools.impl;
002
003import org.biopax.paxtools.model.BioPAXElement;
004import org.biopax.paxtools.util.BPCollections;
005import org.hibernate.annotations.DynamicInsert;
006import org.hibernate.annotations.DynamicUpdate;
007import org.hibernate.annotations.Proxy;
008
009import javax.persistence.*;
010import java.security.MessageDigest;
011import java.security.NoSuchAlgorithmException;
012import java.util.Map;
013@Entity
014@Proxy(proxyClass= BioPAXElement.class)
015@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
016@DiscriminatorColumn(length=40)
017@DynamicUpdate
018@DynamicInsert
019@NamedQueries({
020        @NamedQuery(name="org.biopax.paxtools.impl.BioPAXElementExists",
021                                query="select 1 from BioPAXElementImpl where pk=:md5uri")
022        })
023public abstract class BioPAXElementImpl implements BioPAXElement
024{
025        // Full-text search field names (case sensitive!)
026        public static final String FIELD_AVAILABILITY = "availability";
027        public static final String FIELD_COMMENT = "comment"; // biopax comments
028        public static final String FIELD_KEYWORD = "keyword"; //anything, e.g., names, terms, comments, incl. - from child elements 
029        public static final String FIELD_NAME = "name"; // standardName, displayName, other names
030        public static final String FIELD_TERM = "term"; // CV terms
031        public static final String FIELD_XREFDB = "xrefdb"; //xref.db
032        public static final String FIELD_XREFID = "xrefid"; //xref.id
033        public static final String FIELD_ECNUMBER = "ecnumber";
034        public static final String FIELD_SEQUENCE = "sequence";
035        // Full-text search / Filter field names (case sensitive!) -
036        public static final String FIELD_ORGANISM = "organism";
037        public static final String FIELD_DATASOURCE = "dataSource"; //(case sensitive)
038        public static final String FIELD_PATHWAY = "pathway";
039        
040        // Global search filter definitions in the (paxtools-core PhysicalEntityImpl, case sensitive!)
041        public static final String FILTER_BY_ORGANISM = "organism";
042        public static final String FILTER_BY_DATASOURCE = "datasource";
043        
044        private String uri;
045        
046        // anything extra can be stored in this map (not to persist in a DB usually)
047        private Map<String, Object> annotations;
048
049        private String _pk; // Primary Key
050
051        @Version
052        private long version;
053        
054        public BioPAXElementImpl() {
055                this.annotations = BPCollections.I.createMap();
056        }
057        
058
059        // Primary Key for persistence
060        // could not use names like: 'key' (SQL conflict), 'id', 'idx' 
061        // (conflicts with the property in sub-classes).
062        // @GeneratedValue - did not work (for stateless sessions)      
063    /**
064     * Gets Primary Key.
065     * 
066     * This method may be useful within a
067     * persistence context (Hibernate). This is not part of
068     * Paxtools standard BioPAX API, it is implementation detail.
069     * 
070     * Normally, one should always use {@link #getRDFId()}
071     * to get BioPAX element's URI and use it in 
072     * data analysis.
073     * 
074     * @return primary key (for persistence)
075     */
076        @Id
077        @Column(name="pk", length=32) // enough to save MD5 digest Hex.
078        public String getPk() {
079                return _pk;
080        }
081
082        
083        /**
084         * Primary Key setter (non-public method).
085         * 
086         * This is called only from {@link #setRDFId(String)} 
087         * and Hibernate framework (optional). Primary Key is not required 
088         * (can be ignored) if this BioPAX element (model, algorithm) 
089         * is not persistent (i.e., when not using any persistence provider, 
090         * database, etc. to handle the BioPAX model)
091         * 
092         * 
093         * @param pk
094         */
095        @SuppressWarnings("unused")
096        private void setPk(String pk) {
097                this._pk = pk;
098        }
099
100        //private simple setter/getter, for persistence only
101        //(use biopax element RDFId property instead)
102        @Lob
103        @Column(nullable=false)
104    private String getUri() {
105        return uri;
106    }
107    @SuppressWarnings("unused")
108        private void setUri(String uri) {
109        this.uri = uri;
110    }   
111        
112        @Transient
113    public boolean isEquivalent(BioPAXElement element)
114    {
115        return this.equals(element) || this.semanticallyEquivalent(element);
116    }
117
118    protected boolean semanticallyEquivalent(BioPAXElement element)
119    {
120        return false;
121    }
122
123    public int equivalenceCode()
124    {
125        return uri.hashCode();
126    }
127
128
129        // Beware PROBLEMs, do not use RDFId (URI) as primary key 
130        // (e.g., Mysql 5.x PK/index is case-insensitive, only 64-chars long, by default;
131    // and there're performance issues too)
132    @Transient
133    public String getRDFId()
134    {
135        return uri;
136    }
137
138    /**
139     * Private setter for the biopax element RDFId 
140     * (full URI). Using the URI string, this setter 
141     * also sets or updates the primary key field, 
142     * {@link #getPk()}
143     * 
144     * Normally, URI should never be modified 
145     * after the object is created unless you know 
146     * what you're doing (and can use Java Reflection).
147     * 
148     * @param uri
149     */
150    @SuppressWarnings("unused")
151        private synchronized void setRDFId(String uri)
152    {
153        if(uri == null)
154                throw new IllegalArgumentException();
155        
156        this.uri = uri;
157        this._pk = md5hex(this.uri);
158    }
159
160    
161    public String toString()
162    {
163        return uri;
164    }
165
166    
167    @Transient
168    public Map<String, Object> getAnnotations() {
169                return annotations;
170        }
171    
172
173        /**
174         * Utility method that is called once per object
175         * to generate the primary key {@link #getPk()}
176         * from URI. URIs can be long and are case sensitive,
177         * and therefore would make bad DB primary key.
178         * 
179         * @param id
180         * @return
181         */
182        private static String md5hex(String id) {
183                
184                // MessageDigest is not thread-safe and cheap to build 
185                // - so we create a instance every time here:
186                MessageDigest md5; 
187                try {
188                        md5 = MessageDigest.getInstance("MD5");
189                } catch (NoSuchAlgorithmException e) {
190                        throw new RuntimeException("Cannot instantiate MD5 MessageDigest!", e);
191                }
192                
193                byte[] digest = md5.digest(id.getBytes());
194                
195                StringBuffer sb = new StringBuffer();
196                for (byte b : digest)
197                        sb.append(Integer.toHexString((int) (b & 0xff) | 0x100).substring(1, 3));
198                
199                return sb.toString();
200        }
201    
202        
203        /**
204         * true if and only if the other obj has the same biopax type 
205         * (same {@link #getModelInterface()}, not a subclass) and 
206         * same URI. Other properties are not considered.
207         * 
208         */
209        @Override
210        public boolean equals(Object obj) {
211                return (obj instanceof BioPAXElement) 
212                        && this.getModelInterface() == ((BioPAXElement) obj).getModelInterface()
213                        && this.uri.equals(((BioPAXElement) obj).getRDFId());
214        }
215        
216        
217        /**
218         * This method is consistent with the 
219         * overridden {@link #equals(Object)} method
220         * (biopax type and URI are what matters) 
221         */
222        @Override
223        public int hashCode() {
224                return (getModelInterface() + uri).hashCode();
225        }
226
227}
228