001package org.biopax.paxtools.io.jena;
002
003import com.hp.hpl.jena.datatypes.xsd.XSDDatatype;
004import com.hp.hpl.jena.ontology.*;
005import com.hp.hpl.jena.rdf.model.*;
006import com.hp.hpl.jena.util.iterator.ExtendedIterator;
007import org.apache.commons.logging.Log;
008import org.apache.commons.logging.LogFactory;
009import org.biopax.paxtools.controller.AbstractPropertyEditor;
010import org.biopax.paxtools.controller.PropertyEditor;
011import org.biopax.paxtools.io.BioPAXIOHandlerAdapter;
012import org.biopax.paxtools.model.BioPAXElement;
013import org.biopax.paxtools.model.BioPAXFactory;
014import org.biopax.paxtools.model.BioPAXLevel;
015import org.biopax.paxtools.model.Model;
016import org.biopax.paxtools.util.IllegalBioPAXArgumentException;
017
018import java.io.InputStream;
019import java.io.OutputStream;
020import java.lang.reflect.InvocationTargetException;
021import java.util.HashMap;
022import java.util.Map;
023import java.util.Set;
024
025import static org.biopax.paxtools.model.BioPAXLevel.isInBioPAXNameSpace;
026
027
028/**
029 * Provides I/O support for BioPAX models presented in OWL format using the {@link com.hp.hpl.jena}
030 * package.
031 */
032public final class JenaIOHandler extends BioPAXIOHandlerAdapter
033{
034// ------------------------------ FIELDS ------------------------------
035
036        private static final OntModelSpec spec= new OntModelSpec(OntModelSpec.OWL_DL_MEM);;
037
038
039        private static final Log log = LogFactory.getLog(JenaIOHandler.class);
040
041        // ------------------------------ MAPS ------------------------------
042        private HashMap<Object, Individual> objectToIndividualMap;
043
044        OntModel ontModel;
045
046        /**
047         * If set to true, "error-mode: strict" option will be passed to Jena reader.
048         */
049
050        // --------------------------- CONSTRUCTORS ---------------------------
051        public JenaIOHandler()
052        {
053                this(null, null);
054        }
055
056        public JenaIOHandler(BioPAXLevel level)
057        {
058                this(level.getDefaultFactory(), level);
059        }
060
061        public JenaIOHandler(BioPAXFactory factory, BioPAXLevel level)
062        {
063                super(factory, level);
064        }
065
066        // -------------------------- OTHER METHODS --------------------------
067
068        @Override
069        protected void resetEditorMap()
070        {
071                setEditorMap(new JenaEditorMap(this.getLevel()));
072        }
073
074        protected void init(InputStream in)
075        {
076                ontModel = readJenaModel(in);
077        }
078
079        protected Map<String, String> readNameSpaces()
080        {
081                Map<String, String> namespaces = new HashMap<String, String>();
082                namespaces.putAll(ontModel.getNsPrefixMap());
083                return namespaces;
084        }
085
086
087        protected void createAndBind(Model model)
088        {
089                try
090                {
091                        createObjects(ontModel, model);
092                        bindPropertiesToObjects(ontModel, model);
093                }
094                catch (IllegalBioPAXArgumentException e)
095                {
096                        log.error(e);
097                }
098        }
099
100        /**
101         * Writes a model in OWL format using the an output stream.
102   * @param model model to be converted into OWL format
103   * @param outputStream output stream to which the model will be written
104   */
105        public void convertToOWL(Model model, OutputStream outputStream)
106        {
107                objectToIndividualMap = new HashMap<Object, Individual>();
108                OntModel ontModel = initializeEmptyOntModel(model);
109                createIndividuals(ontModel, model);
110                bindObjectsToProperties(ontModel, model);
111                RDFWriter writer = ontModel.getWriter("RDF/XML-ABBREV");
112                writer.setProperty("relativeURIs", "same-document, relative, parent, absolute");
113                String base = model.getXmlBase();
114                if (log.isDebugEnabled())
115                {
116                        log.debug("base = " + base);
117                }
118                if(base != null && !"".equals(base))
119                        writer.setProperty("xmlbase", base);
120                writer.setProperty("showXmlDeclaration", "true");
121                writer.write(ontModel, outputStream, base);
122                //objectToIndividualMap = null;
123        }
124
125        public OntModel readJenaModel(InputStream in)
126        {
127                OntModel ontModel = createModel();
128//              ontModel.setStrictMode(true);
129//              ontModel.getReader().setProperty("error-mode", "strict");
130                ontModel.read(in, "");
131                //ontModel.loadImports();
132                return ontModel;
133        }
134
135
136        private void createObjects(OntModel ontModel, Model model)
137        {
138                ExtendedIterator extendedIterator = ontModel.listIndividuals();
139                while (extendedIterator.hasNext())
140                {
141                        Individual individual = (Individual) extendedIterator.next();
142                        OntClass ontClass = (OntClass) individual.getRDFType().as(OntClass.class);
143                        createAndAdd(model, individual.getURI(), ontClass.getLocalName());
144                }
145        }
146
147
148        private void bindPropertiesToObjects(OntModel ontModel, Model model) throws IllegalBioPAXArgumentException
149        {
150                ExtendedIterator extendedIterator = ontModel.listIndividuals();
151                while (extendedIterator.hasNext())
152                {
153                        Individual individual = (Individual) extendedIterator.next();
154                        BioPAXElement bpe = model.getByID(individual.getURI());
155                        StmtIterator stmtIterator = individual.listProperties();
156                        while (stmtIterator.hasNext())
157                        {
158                                Statement statement = (Statement) stmtIterator.next();
159                                Property predicate = statement.getPredicate();
160
161                                try
162                                {
163                                        if (isInBioPAXNameSpace(predicate.getNameSpace()))
164                                        {
165                                                bindProperty(predicate, bpe, individual, model);
166                                        } else if (predicate.getLocalName().equals("comment") && predicate.getNameSpace().equals(
167                                                        "http://www.w3.org/1999/02/22-rdf-syntax-ns#"))
168                                        {
169                                                AbstractPropertyEditor editor = getRDFCommentEditor(bpe);
170
171                                                OntProperty ontProperty;
172                                                try
173                                                {
174                                                        ontProperty = ((OntProperty) predicate.as(OntProperty.class));
175                                                }
176                                                catch (ConversionException e)
177                                                {
178                                                        throw new IllegalBioPAXArgumentException(
179                                                                        "Unknown property! " + predicate + " bpe:" + bpe.getRDFId(), e);
180                                                }
181                                                checkCardinalityAndBindValue(bpe, individual, model, ontProperty, editor);
182                                        } else if (log.isDebugEnabled())
183                                        {
184                                                log.debug("Skipping non-biopax statement:" + predicate);
185                                        }
186
187                                }
188                                catch (IllegalBioPAXArgumentException e)
189                                {
190
191                                        log.error("Conversion error. " + e);
192
193                                }
194                                catch (IllegalAccessException ex)
195                                {
196                                        throw new IllegalBioPAXArgumentException("Conversion failed.", ex);
197                                }
198                                catch (InvocationTargetException ex)
199                                {
200                                        throw new IllegalBioPAXArgumentException("Conversion failed.", ex);
201                                }
202                        }
203                }
204        }
205
206
207        private void bindProperty(Property predicate, BioPAXElement bpe, Individual individual, Model model)
208                        throws IllegalAccessException, InvocationTargetException
209        {
210                OntProperty ontProperty;
211                try
212                {
213                        ontProperty = ((OntProperty) predicate.as(OntProperty.class));
214                }
215                catch (ConversionException e)
216                {
217                        throw new IllegalBioPAXArgumentException("Unknown property! " + predicate + " bpe:" + bpe.getRDFId(), e);
218                }
219                String localName = ontProperty.getLocalName();
220                PropertyEditor editor = this.getEditorMap().getEditorForProperty(localName, bpe.getModelInterface());
221                if (editor != null)
222                {
223                        checkCardinalityAndBindValue(bpe, individual, model, ontProperty, editor);
224                } else
225                {
226                        throw new IllegalBioPAXArgumentException(
227                                        "Could not locate editor! " + predicate + " element:" + bpe.getRDFId() + " property:" +
228                                        localName);
229                }
230        }
231
232        private void checkCardinalityAndBindValue(BioPAXElement bpe, Individual individual, Model model,
233                                                  OntProperty ontProperty, PropertyEditor editor)
234        {
235                if (editor.isMultipleCardinality())
236                {
237                        NodeIterator nodeIterator = individual.listPropertyValues(ontProperty);
238                        while (nodeIterator.hasNext())
239                        {
240                                RDFNode propertyValue = (RDFNode) nodeIterator.next();
241                                bindValue(propertyValue, editor, bpe, model);
242                        }
243                } else
244                {
245                        RDFNode propertyValue = individual.getPropertyValue(ontProperty);
246                        bindValue(propertyValue, editor, bpe, model);
247                }
248        }
249
250        private void bindValue(RDFNode propertyValue, PropertyEditor editor, BioPAXElement bpe, Model model)
251        {
252
253                String stringValue = null;
254                if (propertyValue.isResource())
255                {
256                        stringValue = ((Resource) propertyValue).getURI();
257
258                } else if (propertyValue.isLiteral())
259                {
260                        stringValue = ((Literal) propertyValue).getString();
261                } else
262                {
263                        log.error("Unexpected state." + propertyValue + " is not a resource or literal.");
264                }
265
266                bindValue(stringValue, editor, bpe, model);
267
268        }
269
270
271// --------------------------- WRITER METHODS ---------------------------
272
273        private OntModel initializeEmptyOntModel(Model model)
274        {
275                OntModel ontModel = createModel();
276
277                String xmlBase = model.getXmlBase();
278                if (xmlBase == null || xmlBase.equals(""))
279                {
280                        xmlBase = "http://biopax.org/paxtools#";
281                }
282                Ontology base = ontModel.createOntology(xmlBase);
283                String uri = model.getLevel().getNameSpace();
284                uri = uri.substring(0, uri.length() - 1);
285                if (log.isDebugEnabled())
286                {
287                        log.debug("uri = " + uri);
288                }
289                ontModel.setNsPrefixes(model.getNameSpacePrefixMap());
290                base.addImport(ontModel.createResource(uri));
291                ontModel.loadImports();
292                return ontModel;
293        }
294
295        private void createIndividuals(OntModel ontModel, Model model)
296        {
297                for (BioPAXElement bp : model.getObjects())
298                {
299                        String name = bp.getModelInterface().getName();
300                        name = name.substring(name.lastIndexOf('.') + 1);
301                        OntClass ontClass = ontModel.getOntClass(this.getLevel().getNameSpace() + name);
302                        if (log.isTraceEnabled())
303                        {
304                                log.trace("ontClass = " + ontClass);
305                        }
306
307                        Individual individual = ontModel.createIndividual(bp.getRDFId(), ontClass);
308                        if (log.isTraceEnabled())
309                        {
310                                log.trace("individual = " + individual);
311                        }
312
313                        objectToIndividualMap.put(bp, individual);
314                }
315        }
316
317        private void bindObjectsToProperties(OntModel ontModel, Model model)
318        {
319                for (BioPAXElement bean : model.getObjects())
320                {
321                        Set<PropertyEditor> beanEditors = this.getEditorMap().getEditorsOf(bean);
322
323                        for (PropertyEditor propertyEditor : beanEditors)
324                        {
325                                insertStatement(propertyEditor, bean, ontModel);
326                        }
327                }
328        }
329
330        /**
331         * Builds an OWL statement from the given <em>bean</em> using the <em>editor</em> and inserts it
332         * into an ontology model.
333         * @param editor editor to be used for the statement build
334         * @param bean bean which the statement is about
335         * @param ontModel ontology model into which the built statement will be inserted
336         */
337        private void insertStatement(PropertyEditor editor, BioPAXElement bean, OntModel ontModel)
338
339        {
340                Set value = editor.getValueFromBean(bean);// value cannot be null
341                for (Object valueElement : value)
342                {
343                        if (!editor.isUnknown(valueElement)) buildStatementFor(bean, editor, valueElement, ontModel);
344                }
345        }
346
347
348        private void buildStatementFor(BioPAXElement bean, PropertyEditor editor, Object value, OntModel ontModel)
349        {
350                assert (bean != null && editor != null);
351                Property property = ontModel.getProperty(this.getLevel().getNameSpace() + editor.getProperty());
352                Individual ind = objectToIndividualMap.get(bean);
353                Class range = editor.getRange();
354                JenaEditorMap editorMap = (JenaEditorMap) this.getEditorMap();
355                XSDDatatype dataType = editorMap.getDataTypeFor(editor);
356                if (dataType != null)
357                {
358                        ind.addProperty(property, ontModel.createTypedLiteral(value.toString(), dataType));
359                } else
360                {
361                        Individual valueInd = objectToIndividualMap.get(value);
362                        if (valueInd == null) // TODO not sure about Boolean case here, but this makes tests pass...
363                        {
364                                throw new IllegalBioPAXArgumentException(
365                                                range + " : for value '" + value + "' coresponding individual value is NULL " +
366                                                "(objectToIndividualMap)");
367                        }
368
369                        ind.addProperty(property, valueInd);
370                }
371        }
372        static OntModel createModel()
373    {
374        return ModelFactory.createOntologyModel(spec);
375    }
376}
377
378