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