001/*
002 * $RCSfile: Concept.java,v $
003 * $Revision: 1.43.2.4 $ 
004 * $Date: 2015/05/04 16:22:49 $
005 *
006 * Copyright 2012, 2013 Cloud Software Group, Inc. ALL RIGHTS RESERVED. 
007 * Cloud Software Group, Inc. Confidential Information
008 */
009package com.tibco.xp.runtime;
010
011import com.kabira.platform.KeyFieldValueList;
012import com.kabira.platform.KeyManager;
013import com.kabira.platform.KeyQuery;
014import com.kabira.platform.KeyOrderedBy;
015import com.kabira.platform.LockMode;
016import com.kabira.platform.ManagedObject;
017import com.kabira.platform.ObjectNotUniqueError;
018import com.kabira.platform.ResourceUnavailableException;
019import com.kabira.platform.annotation.Managed;
020import com.tibco.cep.kernel.model.knowledgebase.DuplicateExtIdException;
021import com.tibco.cep.kernel.service.logging.Level;
022import com.tibco.cep.runtime.model.element.Property;
023import com.tibco.cep.runtime.model.element.PropertyArray;
024import com.tibco.cep.runtime.model.element.PropertyAtom;
025import com.tibco.cep.runtime.model.element.PropertyAtomConcept;
026import com.tibco.cep.runtime.model.element.impl.ConceptImpl;
027import com.tibco.cep.runtime.model.element.impl.ManagedObjectLockType;
028import com.tibco.cep.runtime.model.element.impl.property.simple.PropertyArrayConceptReferenceSimple;
029import com.tibco.cep.runtime.model.element.impl.property.simple.PropertyArrayContainedConceptSimple;
030
031import java.util.Date;
032import java.util.HashMap;
033import java.util.Set;
034
035/**
036 * Concept base class
037 */
038@Managed
039public abstract class Concept extends Entity
040{
041        /**
042         * Lookup operation type - unique, or not.
043         */
044        protected enum LookupType
045        {
046            /**
047             * Perform a unique key lookup
048             */
049                UNIQUE_LOOKUP,
050                /**
051                 * Perform a non-unique key lookup
052                 */
053                NONUNIQUE_LOOKUP
054        };
055        
056        /**
057         * Java constructor
058         */
059        protected Concept()
060        {
061        }
062        
063        /**
064         * Rule or Rule Function constructor
065         * @param id Unique identifier
066         */
067        protected Concept(long id)
068        {
069            register(id);
070        }
071
072        /**
073     * Assert a concept and start a run-to-completion cycle.  
074     * <p>
075     * This starts a new run-to-completion (RTC) cycle.  A
076     * new transaction is started if one is not already active.
077     * The RTC cycle is complete when this
078     * method returns.  RETE working memory is cleared at the end
079     * of the RTC cycle.
080     * <p>
081     * If the concept was previously loaded into working memory
082     * this method quietly does nothing.
083     * @throws ObjectNotUniqueError Duplicate external identifier
084     * @throws ResourceUnavailableException Rules engine not running
085     */
086    public final void assertConcept() 
087            throws ObjectNotUniqueError, ResourceUnavailableException
088    {
089        try
090        {
091            assertEntity(true);
092        }
093        catch (DuplicateExtIdException e)
094        {
095            throw new ObjectNotUniqueError(e.getLocalizedMessage());
096        }
097    }
098    
099    /**
100     * Assert a concept, but do not start a run-to-completion cycle.
101     * <p>
102     * Assert a concept into the RETE, but do not start a
103     * run-to-completion cycle.
104     * <p>
105     * If the concept was previously loaded into working memory
106     * this method quietly does nothing.
107     * @throws ObjectNotUniqueError Duplicate external identifier
108     * @throws ResourceUnavailableException Rules engine not running
109     */
110    public final void assertConceptWithoutRunToCompletion() 
111            throws ObjectNotUniqueError, ResourceUnavailableException
112    {
113        try
114        {
115            assertEntity(false);
116        }
117        catch (DuplicateExtIdException e)
118        {
119            throw new ObjectNotUniqueError(e.getLocalizedMessage());
120        }
121    }
122    
123        @Override
124        public String toString()
125        {
126                StringBuilder value = new StringBuilder(super.toString());
127
128                value.append("&objectreference=");
129                value.append(ManagedObject.objectToReference(this));
130                return value.toString();
131        }
132
133        /**
134         * Execute a query.
135         * <p>
136         * Unique queries return a concept or, null if not found.  The concept is
137         * added to working memory in the current RTC.
138         * <p>
139         * Non-unique or ordered queries return an iterator.  Concepts
140         * returned in the iterator or not added to working memory
141         * in the current RTC until they are accessed via the returned
142         * iterator.
143         * @param klass Concept class.
144         * @param keyName Key name.
145         * @param lockMode The lock mode to use when performing the query.
146         * @param lookupType Specify whether this is a unique or non-unique query.
147         * @param direction Ordering for non-unique queries.
148         * @param keyFields Query key fields.
149         * @return A concept for a unique query or null, if not found.
150         * An iterator for a non-unique query.
151         */
152        // TODO - Break into two separate methods - one for unique and one for non-unique
153        protected static Object lookupQuery(
154                final Class<?> klass,
155                        final String keyName,
156                        final String lockMode,
157                        final LookupType lookupType,
158                        final String direction,
159                        final HashMap<String, Object> keyFields)
160        {
161                //
162                // define the query
163                //
164                KeyManager keyManager = new KeyManager();
165                KeyQuery<?> keyQuery = keyManager.createKeyQuery(klass, keyName);
166                KeyFieldValueList keyFieldValueList = new KeyFieldValueList();
167
168                //
169                // fill in the key fields
170                //
171                Set<String> fieldNames = keyFields.keySet();
172                for (String fieldName : fieldNames)
173                {
174                        keyFieldValueList.add(fieldName, keyFields.get(fieldName));
175                }
176                keyQuery.defineQuery(keyFieldValueList);
177                
178                if (TransactionService.isLoggerEnabledFor(Level.TRACE) == true)
179        {
180                TransactionService.log(Level.TRACE, "Query (" + mapLockMode(lockMode) + "): " 
181                        + keyQuery.toString());
182        }
183
184                //
185                // if this is a non-unique query, just return the iterator.
186                //
187                if (lookupType == LookupType.NONUNIQUE_LOOKUP)
188                {
189                        if (direction != null)
190                        {
191                                return keyQuery.getResults(mapOrderBy(direction), mapLockMode(lockMode)).iterator();
192                        }
193                        return keyQuery.getResults(mapLockMode(lockMode)).iterator();
194                }
195                
196                assert lookupType == LookupType.UNIQUE_LOOKUP : klass;
197
198                Concept concept = (Concept)keyQuery.getSingleResult(mapLockMode(lockMode));
199
200                if (concept == null)
201                {
202                        return null;
203                }
204                
205                //
206                //    Load the concept into working memory
207                //
208                concept.load();
209                
210                return concept;
211        }
212        
213        /**
214     * Determine the cardinality of the specified query
215     * @param klass Concept class
216     * @param keyName Key name
217     * @param keyFields Key fields
218     * @return The cardinality for the specified key and values.
219     */
220    protected static Integer cardinality(
221            final Class<?> klass,
222            final String keyName,
223            final HashMap<String, Object> keyFields)
224    {
225        //
226        // define the query
227        //
228        KeyManager keyManager = new KeyManager();
229        KeyQuery keyQuery = keyManager.createKeyQuery((Class<?>) klass, keyName);
230        KeyFieldValueList keyFieldValueList = new KeyFieldValueList();
231
232        //
233        // fill in the key fields
234        //
235        Set<String> fieldNames = keyFields.keySet();
236        for (String fieldName : fieldNames)
237        {
238            keyFieldValueList.add(fieldName, keyFields.get(fieldName));
239        }
240        keyQuery.defineQuery(keyFieldValueList);
241        
242                if (TransactionService.isLoggerEnabledFor(Level.TRACE) == true)
243        {
244                TransactionService.log(Level.TRACE, "Cardinality: " + keyQuery.toString());
245        }
246        
247        return keyQuery.cardinality();
248    }
249
250        /**
251         * Map the lock mode value
252         * @param lockMode User provided lock mode value
253         * @return Mapped lock mode
254         * <p>
255         * A user provided lockMode value of read is mapped to a READLOCK.
256         * <p>
257         * A user provided lockMode value of write is mapped to WRITELOCK
258         * <p>
259         * All other values are mapped to NOLOCK.
260         */
261    // TODO - This should not be public
262        public static LockMode mapLockMode(final String lockMode)
263        {
264            LockMode mappedValue = LockMode.NOLOCK;
265            
266            if (lockMode.equals("read") == true)
267            {
268                mappedValue = LockMode.READLOCK;
269            }
270            
271            if (lockMode.equals("write") == true)
272            {
273                mappedValue = LockMode.WRITELOCK;
274            }
275            
276            return mappedValue;
277        }
278        
279
280        /**
281     * Map the order-by direction
282     * @param direction User provided order-by direction
283     * @return Mapped order-by direction
284     * <p>
285     * A user provided direction value of descending is mapped to a DESCENDING.
286     * <p>
287     * All other values are mapped to ASCENDING.
288     */
289    protected static KeyOrderedBy mapOrderBy(final String direction)
290    {
291        KeyOrderedBy mappedValue = KeyOrderedBy.ASCENDING;
292        
293        if (direction.equals("descending") == true)
294        {
295            mappedValue = KeyOrderedBy.DESCENDING;
296        }
297        
298        return mappedValue;
299    }
300
301        /**
302     * Refresh the concept
303     * <p>
304     * Concept values are refreshed from shared memory.
305     * @param original Concept to refresh.
306     * @return Refreshed concept.  May be null.
307     */
308    // TODO - Currently required to be public because it is used in the
309    // wrapper catalog function stubs
310    public static com.tibco.cep.runtime.model.element.Concept
311        refresh(com.tibco.cep.runtime.model.element.Concept original)
312    {
313        if (original == null)
314        {
315            return null;
316        }
317        
318        assert original instanceof ConceptImpl == true : original;
319         
320        //
321        //  Fetch a current view of this entity from shared memory
322        //
323        ConceptImpl current = (ConceptImpl)TransactionService.getInstance().fetchById(
324                original.getId(), 
325                original.getClass(), 
326                ManagedObjectLockType.READLOCK);
327        
328        //
329        //      Nothing do to if concept doesn't exist in shared memory - it was deleted
330        //      directly in Java code
331        //
332        if (current == null)
333        {
334                return null;
335        }
336        
337        assert original.getClass().getName().equals(current.getClass().getName()) == true : original;
338        assert original.getProperties().length == current.getProperties().length : original;
339
340        //
341        //  Update the original handle with the current view from shared memory
342        //
343        for (Property p : current.getProperties())
344        {
345            //
346            //  Refresh any references (or contained) concepts
347            //
348            if (p instanceof PropertyAtomConcept)
349            {
350                PropertyAtomConcept to = (PropertyAtomConcept)original.getPropertyAtom(p.getName());
351                assert to != null : p.getName();
352                
353                if (to.getValue() == null)
354                {
355                        
356                        to.setValue(((PropertyAtomConcept) p).getValue());
357                        continue;
358                }
359                refresh((com.tibco.cep.runtime.model.element.Concept)to.getValue());
360            }
361            //
362            //  Refresh arrays containing concepts
363            //
364            else if ((p instanceof PropertyArrayConceptReferenceSimple)
365                        || (p instanceof PropertyArrayContainedConceptSimple))
366            {
367                PropertyArray from = (PropertyArray)p;
368                PropertyArray to = original.getPropertyArray(p.getName());
369                assert to != null : p.getName();
370                PropertyAtom [ ] atoms = from.toArray();
371                
372                if ((atoms == null) || (atoms.length == 0))
373                {
374                        copyArray(from, to);
375                        continue;
376                }
377                com.tibco.cep.runtime.model.element.Concept [ ] concepts 
378                        = new com.tibco.cep.runtime.model.element.Concept[atoms.length];
379                for (int i = 0; i < atoms.length; i++)
380                {
381                        if (atoms[i].getValue() == null)
382                        {
383                                continue;
384                        }
385                        assert atoms[i].getValue() instanceof ConceptImpl : atoms[i].getValue();
386                        ConceptImpl c = (ConceptImpl)atoms[i].getValue();
387                        
388                        //
389                        //      Clear parent reference.  It will be reset when concept is refreshed
390                        //
391                        c.setParentReference(null);
392                        concepts[i] = refresh(c);
393                }
394                copyArray(from, to);
395            }
396            //
397            //  Primitive types
398            //
399            else if (p instanceof PropertyAtom)
400            {
401                try
402                {
403                    PropertyAtom from = (PropertyAtom)p;
404                    PropertyAtom to = original.getPropertyAtom(p.getName());
405                    assert to != null : p.getName();
406 
407                    if (from.getValue() == null && to.getValue() == null)
408                    {
409                        continue;
410                    }
411                    if (((from.getValue() == null) && (to.getValue() != null))
412                            || (from.getValue().equals(to.getValue()) == false))
413                    {
414                        to.setValue(from.getValue());
415                    }
416                }
417                catch (Exception e)
418                {
419                    e.printStackTrace();
420                }
421            }
422            //
423            //  Arrays of primitive types
424            //
425            else
426            {
427                assert p instanceof PropertyArray : p.getName();
428                PropertyArray from = (PropertyArray)p;
429                PropertyArray to = original.getPropertyArray(p.getName());
430                assert to != null : p.getName();
431                
432                copyArray(from, to);
433            }
434        }
435        return original;
436    }
437    
438    /**
439     * Refresh an array of concepts
440     * <p>
441     * All concepts in the array are refreshed from shared memory
442     * @param concepts Concept array to refresh
443     */
444    // TODO - Currently required to be public because it is used in the 
445    // wrapper catalog function stubs
446    public static void refreshArray(
447            com.tibco.cep.runtime.model.element.Concept [ ] concepts)
448    {
449        if (concepts == null)
450        {
451                return;
452        }
453        
454        for (com.tibco.cep.runtime.model.element.Concept c : concepts)
455        {
456            refresh(c);
457        }
458    }
459    
460    /**
461     * Update a concept in shared memory
462     * <p>
463     * Flush concepts to shared memory
464     * @param concept Concept to flush to shared memory.
465     */
466    // TODO - Currently required to be public because it is used in the
467    // wrapper catalog function stubs
468    public static void update(com.tibco.cep.runtime.model.element.Concept concept)
469    {
470        if (concept == null)
471        {
472            return;
473        }
474        
475                if (TransactionService.isLoggerEnabledFor(Level.TRACE) == true)
476        {
477                TransactionService.log(Level.TRACE, "Updating concept " + concept);
478        }
479        
480        assert concept instanceof ConceptImpl : concept;
481        ConceptImpl conceptImpl = (ConceptImpl)concept;
482        Entity me = Entity.lookupById(conceptImpl.getId());
483        
484        //
485        //  If we didn't find the entity we need to insert it.  This can happen
486        //  if an entity is created in an RTC and then a catalog function is called
487        //
488        if (me == null)
489        {
490            TransactionService.getInstance().insert(conceptImpl);       
491        }
492        else
493        {
494                TransactionService.getInstance().update(conceptImpl);
495        }
496       
497        //
498        //      Recursively update any concept properties
499        //
500        for (Property p : concept.getProperties())
501        {
502            if (p instanceof PropertyAtomConcept)
503            {
504                PropertyAtomConcept c = (PropertyAtomConcept)p;
505                if (c.isSet() == false)
506                {
507                        continue;
508                }
509                update((com.tibco.cep.runtime.model.element.Concept)c.getValue());
510            }
511        }
512    }
513    
514    /**
515     * Update an array of concepts
516     * <p>
517     * All concepts in the array are updated in shared memory 
518     * if they have been modified in the current RTC.
519     * @param concepts Concept array to update
520     */
521    // TODO - Currently required to be public because it is used in the 
522    // wrapper catalog function stubs
523    public static void updateArray(
524            com.tibco.cep.runtime.model.element.Concept [ ] concepts)
525    {
526        if (concepts == null)
527        {
528                return;
529        }
530        
531        for (com.tibco.cep.runtime.model.element.Concept c : concepts)
532        {
533            update(c);
534        }
535    }
536    
537    /**
538     * Set the parent identifier for a contained type
539     * @param parent Parent identifier
540     */
541    protected final void setParent(long parent)
542    {
543        m_parent = parent;
544    }
545    
546    /**
547     * Get the parent identifier for a contained type
548     * @return Parent identifier;
549     */
550    protected final long getParent()
551    {
552        return m_parent;
553    }
554    
555    /**
556     * Map auto-box array to primitive array
557     * @param a Array to map
558     * @return Mapped array
559     */
560    protected final long [ ] toPrimitiveArray(Long [ ] a)
561    {
562        if (a == null)
563        {
564                return null;
565        }
566        long [ ] n = new long[a.length];
567        for (int i = 0; i < a.length; i++)
568        {
569                n[i] = a[i];
570        }
571        return n;
572    }
573    
574    /**
575     * Map auto-box array to primitive array
576     * @param a Array to map
577     * @return Mapped array
578     */
579    protected final int [ ] toPrimitiveArray(Integer [ ] a)
580    {
581        if (a == null)
582        {
583                return null;
584        }
585        int [ ] n = new int[a.length];
586        for (int i = 0; i < a.length; i++)
587        {
588                n[i] = a[i];
589        }
590        return n;
591    }
592    
593    /**
594     * Map auto-box array to primitive array
595     * @param a Array to map
596     * @return Mapped array
597     */
598    protected final boolean [ ] toPrimitiveArray(Boolean [ ] a)
599    {
600        if (a == null)
601        {
602                return null;
603        }
604        boolean [ ] n = new boolean[a.length];
605        for (int i = 0; i < a.length; i++)
606        {
607                n[i] = a[i];
608        }
609        return n;       
610    }
611    
612    /**
613     * Map auto-box array to primitive array
614     * @param a Array to map
615     * @return Mapped array
616     */
617    protected final double [ ] toPrimitiveArray(Double [ ] a)
618    {
619        if (a == null)
620        {
621                return null;
622        }
623        double [ ] n = new double[a.length];
624        for (int i = 0; i < a.length; i++)
625        {
626                n[i] = a[i];
627        }
628        return n;               
629    }
630    
631    /**
632     * Map auto-box array to primitive array
633     * @param a Array to map
634     * @return Mapped array
635     */
636    protected final String [ ] toPrimitiveArray(String [ ] a)
637    {
638        return a;               
639    }
640    
641    /**
642     * Map auto-box array to primitive array
643     * @param a Array to map
644     * @return Mapped array
645     */
646    protected final Date [ ] toPrimitiveArray(Date [ ] a)
647    {
648        return a;               
649    }
650    
651    //
652    //  Copy an array
653    //
654    private static void copyArray(PropertyArray from, PropertyArray to)
655    {   
656        //
657        //      Grow the target array
658        //
659        if (from.length() > to.length())
660        {
661            for (int i = to.length(); i < from.length(); i++)
662            {
663                to.add(i, from.get(i).getValue());
664            }
665        }
666        
667        //
668        //      Shrink the target array
669        //
670        if (to.length() > from.length())
671        {
672                int originalLength = to.length();
673            for (int i = from.length(); i < originalLength; i++)
674            {
675                assert to.length() > 0 : to.length();
676                to.remove(to.length() - 1);
677            }
678        }
679        
680        assert from.length() == to.length() : 
681                from.getName() + "(" + from.length() + "):" 
682                + to.getName() + "(" + to.length() + ")";
683        
684        //
685        //      Update changed values
686        //
687        for (int i = 0; i < from.length(); i++)
688        {
689            if (from.get(i).getValue().equals(to.get(i).getValue()) == false)
690            {
691                to.get(i).setValue(from.get(i).getValue());
692            }
693        }
694    }
695    
696    private long m_parent = IdentifierGenerator.RESERVED_FOR_EMPTY_INDICATOR;
697        private static final long serialVersionUID = 5L;
698}