001/*
002 *  Copyright 2010 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.workflow.store;
017
018import java.util.ArrayList;
019import java.util.Date;
020import java.util.GregorianCalendar;
021import java.util.LinkedHashSet;
022import java.util.List;
023import java.util.Set;
024
025import javax.jcr.Node;
026import javax.jcr.Property;
027import javax.jcr.PropertyIterator;
028import javax.jcr.PropertyType;
029import javax.jcr.RepositoryException;
030import javax.jcr.Session;
031import javax.jcr.Value;
032import javax.jcr.ValueFactory;
033
034import org.ametys.plugins.repository.AmetysRepositoryException;
035
036import com.opensymphony.workflow.spi.Step;
037
038/**
039 * OSWorkflow step with additional properties.<br>
040 */
041public class AmetysStep implements Step
042{
043    /** Custom property prefix. */
044    protected static final String __CUSTOM_PROP_PREFIX = "ametys:";
045    
046    private Node _node;
047    private AbstractJackrabbitWorkflowStore _store;
048    
049    /**
050     * Build an ametys step.
051     * @param node the backing JCR node.
052     * @param store the workflow store.
053     */
054    public AmetysStep(Node node, AbstractJackrabbitWorkflowStore store)
055    {
056        this._node = node;
057        this._store = store;
058    }
059    
060    /**
061     * Get the backing JCR node.
062     * @return the backing JCR node.
063     */
064    Node getNode()
065    {
066        return _node;
067    }
068    
069    @Override
070    public long getId()
071    {
072        try
073        {
074            return _node.getProperty(AbstractJackrabbitWorkflowStore.__ID_PROPERTY).getLong();
075        }
076        catch (RepositoryException e)
077        {
078            throw new AmetysRepositoryException("Error getting the ID property.", e);
079        }
080    }
081    
082    @Override
083    public long getEntryId()
084    {
085        try
086        {
087            return _node.getParent().getProperty(AbstractJackrabbitWorkflowStore.__ID_PROPERTY).getLong();
088        }
089        catch (RepositoryException e)
090        {
091            throw new AmetysRepositoryException("Error getting the entry ID property.", e);
092        }
093    }
094    
095    @Override
096    public int getStepId()
097    {
098        try
099        {
100            return (int) _node.getProperty(AbstractJackrabbitWorkflowStore.__STEP_ID_PROPERTY).getLong();
101        }
102        catch (RepositoryException e)
103        {
104            throw new AmetysRepositoryException("Error getting the step ID property.", e);
105        }
106    }
107    
108    /**
109     * Set the step ID.
110     * @param stepId the step ID to set.
111     */
112    public void setStepId(int stepId)
113    {
114        try
115        {
116            _node.setProperty(AbstractJackrabbitWorkflowStore.__STEP_ID_PROPERTY, stepId);
117        }
118        catch (RepositoryException e)
119        {
120            throw new AmetysRepositoryException("Error setting the step ID property.", e);
121        }
122    }
123    
124    @Override
125    public int getActionId()
126    {
127        try
128        {
129            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__ACTION_ID_PROPERTY))
130            {
131                return (int) _node.getProperty(AbstractJackrabbitWorkflowStore.__ACTION_ID_PROPERTY).getLong();
132            }
133            
134            return 0;
135        }
136        catch (RepositoryException e)
137        {
138            throw new AmetysRepositoryException("Error getting the action ID property.", e);
139        }
140    }
141    
142    /**
143     * Set the action ID.
144     * @param actionId the action ID to set.
145     */
146    public void setActionId(int actionId)
147    {
148        try
149        {
150            _node.setProperty(AbstractJackrabbitWorkflowStore.__ACTION_ID_PROPERTY, actionId);
151        }
152        catch (RepositoryException e)
153        {
154            throw new AmetysRepositoryException("Error setting the action ID property.", e);
155        }
156    }
157    
158    @Override
159    public String getCaller()
160    {
161        try
162        {
163            String caller = null;
164            
165            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__CALLER_PROPERTY))
166            {
167                caller = _node.getProperty(AbstractJackrabbitWorkflowStore.__CALLER_PROPERTY).getString();
168            }
169            
170            return caller;
171        }
172        catch (RepositoryException e)
173        {
174            throw new AmetysRepositoryException("Error getting the caller property.", e);
175        }
176    }
177    
178    /**
179     * Set the caller.
180     * @param caller the caller to set.
181     */
182    public void setCaller(String caller)
183    {
184        try
185        {
186            _node.setProperty(AbstractJackrabbitWorkflowStore.__CALLER_PROPERTY, caller);
187        }
188        catch (RepositoryException e)
189        {
190            throw new AmetysRepositoryException("Error setting the caller property.", e);
191        }
192    }
193    
194    @Override
195    public Date getStartDate()
196    {
197        try
198        {
199            Date value = null;
200            
201            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__START_DATE_PROPERTY))
202            {
203                value = _node.getProperty(AbstractJackrabbitWorkflowStore.__START_DATE_PROPERTY).getDate().getTime();
204            }
205            
206            return value;
207        }
208        catch (RepositoryException e)
209        {
210            throw new AmetysRepositoryException("Error getting the start date property.", e);
211        }
212    }
213    
214    /**
215     * Set the step start date.
216     * @param startDate the start date to set.
217     */
218    public void setStartDate(Date startDate)
219    {
220        try
221        {
222            GregorianCalendar cal = new GregorianCalendar();
223            cal.setTime(startDate);
224            
225            _node.setProperty(AbstractJackrabbitWorkflowStore.__START_DATE_PROPERTY, cal);
226        }
227        catch (RepositoryException e)
228        {
229            throw new AmetysRepositoryException("Error setting the start date property.", e);
230        }
231    }
232    
233    @Override
234    public Date getDueDate()
235    {
236        try
237        {
238            Date value = null;
239            
240            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__DUE_DATE_PROPERTY))
241            {
242                value = _node.getProperty(AbstractJackrabbitWorkflowStore.__DUE_DATE_PROPERTY).getDate().getTime();
243            }
244            
245            return value;
246        }
247        catch (RepositoryException e)
248        {
249            throw new AmetysRepositoryException("Error getting the due date property.", e);
250        }
251    }
252    
253    /**
254     * Set the step due date.
255     * @param dueDate the due date to set.
256     */
257    public void setDueDate(Date dueDate)
258    {
259        try
260        {
261            GregorianCalendar cal = new GregorianCalendar();
262            cal.setTime(dueDate);
263            
264            _node.setProperty(AbstractJackrabbitWorkflowStore.__DUE_DATE_PROPERTY, cal);
265        }
266        catch (RepositoryException e)
267        {
268            throw new AmetysRepositoryException("Error setting the due date property.", e);
269        }
270    }
271    
272    @Override
273    public Date getFinishDate()
274    {
275        try
276        {
277            Date value = null;
278            
279            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__FINISH_DATE_PROPERTY))
280            {
281                value = _node.getProperty(AbstractJackrabbitWorkflowStore.__FINISH_DATE_PROPERTY).getDate().getTime();
282            }
283            
284            return value;
285        }
286        catch (RepositoryException e)
287        {
288            throw new AmetysRepositoryException("Error getting the finish date property.", e);
289        }
290    }
291    
292    /**
293     * Set the step finish date.
294     * @param finishDate the finish date to set.
295     */
296    public void setFinishDate(Date finishDate)
297    {
298        try
299        {
300            GregorianCalendar cal = new GregorianCalendar();
301            cal.setTime(finishDate);
302            
303            _node.setProperty(AbstractJackrabbitWorkflowStore.__FINISH_DATE_PROPERTY, cal);
304        }
305        catch (RepositoryException e)
306        {
307            throw new AmetysRepositoryException("Error setting the finish date property.", e);
308        }
309    }
310    
311    @Override
312    public String getOwner()
313    {
314        try
315        {
316            String owner = null;
317            
318            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__OWNER_PROPERTY))
319            {
320                owner = _node.getProperty(AbstractJackrabbitWorkflowStore.__OWNER_PROPERTY).getString();
321            }
322            
323            return owner;
324        }
325        catch (RepositoryException e)
326        {
327            throw new AmetysRepositoryException("Error getting the owner property.", e);
328        }
329    }
330    
331    /**
332     * Set the owner.
333     * @param owner the owner to set.
334     */
335    public void setOwner(String owner)
336    {
337        try
338        {
339            _node.setProperty(AbstractJackrabbitWorkflowStore.__OWNER_PROPERTY, owner);
340        }
341        catch (RepositoryException e)
342        {
343            throw new AmetysRepositoryException("Error setting the owner property.", e);
344        }
345    }
346    
347    @Override
348    public long[] getPreviousStepIds()
349    {
350        try
351        {
352            List<Long> previousStepsIds = new ArrayList<>();
353            
354            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__PREVIOUS_STEPS_PROPERTY))
355            {
356                Value[] previousSteps = _node.getProperty(AbstractJackrabbitWorkflowStore.__PREVIOUS_STEPS_PROPERTY).getValues();
357    
358                for (Value previousStep : previousSteps)
359                {
360                    Node historyStep = _node.getSession().getNodeByIdentifier(previousStep.getString());
361    
362                    long previousStepId = historyStep.getProperty(AbstractJackrabbitWorkflowStore.__ID_PROPERTY).getLong();
363                    previousStepsIds.add(previousStepId);
364                }
365            }
366            
367            long[] previousIds = new long[previousStepsIds.size()];
368            
369            for (int i = 0; i < previousIds.length; i++)
370            {
371                previousIds[i] = previousStepsIds.get(i);
372            }
373            
374            return previousIds;
375        }
376        catch (RepositoryException e)
377        {
378            throw new AmetysRepositoryException("Error getting the previous IDs property.", e);
379        }
380    }
381    
382    /**
383     * Set the previous step IDs.
384     * @param previousStepIds the previous step IDs to set.
385     */
386    public void setPreviousStepIds(long[] previousStepIds)
387    {
388        try
389        {
390            Session session = _node.getSession();
391            ValueFactory valueFactory = session.getValueFactory();
392            
393            Value[] previousStepsRefs = new Value[0];
394            
395            if (previousStepIds != null)
396            {
397                previousStepsRefs = new Value[previousStepIds.length];
398                
399                for (int i = 0; i < previousStepIds.length; i++)
400                {
401                    long previousStepId = previousStepIds[i];
402                    
403                    // Retrieve existing step for this entry
404                    Node previousStep = _store.getHistoryStepNode(session, getEntryId(), previousStepId);
405                    
406                    // Use the ValueFactory to set a reference to this step
407                    previousStepsRefs[i] = valueFactory.createValue(previousStep);
408                }
409            }
410            
411            _node.setProperty(AbstractJackrabbitWorkflowStore.__PREVIOUS_STEPS_PROPERTY, previousStepsRefs);
412        }
413        catch (RepositoryException e)
414        {
415            throw new AmetysRepositoryException("Error setting the previous IDs property.", e);
416        }
417    }
418    
419    @Override
420    public String getStatus()
421    {
422        try
423        {
424            String status = null;
425            
426            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__STATUS_PROPERTY))
427            {
428                status = _node.getProperty(AbstractJackrabbitWorkflowStore.__STATUS_PROPERTY).getString();
429            }
430            
431            return status;
432        }
433        catch (RepositoryException e)
434        {
435            throw new AmetysRepositoryException("Error getting the status property.", e);
436        }
437    }
438    
439    /**
440     * Set the status.
441     * @param status the status to set.
442     */
443    public void setStatus(String status)
444    {
445        try
446        {
447            _node.setProperty(AbstractJackrabbitWorkflowStore.__STATUS_PROPERTY, status);
448        }
449        catch (RepositoryException e)
450        {
451            throw new AmetysRepositoryException("Error setting the status property.", e);
452        }
453    }
454    
455    /**
456     * Get a custom property value.
457     * @param name the property name.
458     * @return the custom property value.
459     */
460    public Object getProperty(String name)
461    {
462        try
463        {
464            Object value = null;
465            String qName = __CUSTOM_PROP_PREFIX + name;
466            
467            if (_node.hasProperty(qName))
468            {
469                Property property = _node.getProperty(qName);
470                if (property.getDefinition().isMultiple())
471                {
472                    value = getMultiValuedProperty(property);
473                }
474                else
475                {
476                    value = getSingleValuedProperty(property);
477                }
478            }
479            
480            return value;
481        }
482        catch (RepositoryException e)
483        {
484            throw new AmetysRepositoryException("Error setting the custom property: " + name, e);
485        }
486    }
487    
488    /**
489     * Set a custom property value.
490     * @param name the property name.
491     * @param value the value to set.
492     */
493    public void setProperty(String name, Object value)
494    {
495        try
496        {
497            ValueFactory valueFactory = _node.getSession().getValueFactory();
498            String qName = __CUSTOM_PROP_PREFIX + name;
499            
500            if (value instanceof Boolean)
501            {
502                _node.setProperty(qName, (Boolean) value);
503            }
504            else if (value instanceof boolean[])
505            {
506                boolean[] v = (boolean[]) value;
507                Value[] values = new Value[v.length];
508                for (int i = 0; i < v.length; i++)
509                {
510                    values[i] = valueFactory.createValue(v[i]);
511                }
512                
513                _node.setProperty(qName, values);
514            }
515            else if (value instanceof Date)
516            {
517                GregorianCalendar gc = new GregorianCalendar();
518                gc.setTime((Date) value);
519                _node.setProperty(qName, gc);
520            }
521            else if (value instanceof Date[])
522            {
523                Date[] v = (Date[]) value;
524                Value[] values = new Value[v.length];
525                for (int i = 0; i < v.length; i++)
526                {
527                    GregorianCalendar gc = new GregorianCalendar();
528                    gc.setTime(v[i]);
529                    values[i] = valueFactory.createValue(gc);
530                }
531                
532                _node.setProperty(qName, values);
533            }
534            else if (value instanceof Long)
535            {
536                _node.setProperty(qName, (Long) value);
537            }
538            else if (value instanceof long[])
539            {
540                long[] v = (long[]) value;
541                Value[] values = new Value[v.length];
542                for (int i = 0; i < v.length; i++)
543                {
544                    values[i] = valueFactory.createValue(v[i]);
545                }
546                
547                _node.setProperty(qName, values);
548            }
549            else if (value instanceof String[])
550            {
551                String[] v = (String[]) value;
552                Value[] values = new Value[v.length];
553                for (int i = 0; i < v.length; i++)
554                {
555                    values[i] = valueFactory.createValue(v[i]);
556                }
557                
558                _node.setProperty(qName, values);
559            }
560            else
561            {
562                _node.setProperty(qName, value.toString());
563            }
564        }
565        catch (RepositoryException e)
566        {
567            throw new AmetysRepositoryException("Error setting the custom property: " + name, e);
568        }
569    }
570    
571    /**
572     * Get all the custom property names.
573     * @return the custom property names.
574     */
575    public Set<String> getPropertyNames()
576    {
577        try
578        {
579            Set<String> propertyNames = new LinkedHashSet<>();
580            
581            PropertyIterator properties = _node.getProperties(__CUSTOM_PROP_PREFIX + "*");
582            while (properties.hasNext())
583            {
584                String qName = properties.nextProperty().getName();
585                String name = qName.substring(__CUSTOM_PROP_PREFIX.length());
586                propertyNames.add(name);
587            }
588            
589            return propertyNames;
590        }
591        catch (RepositoryException e)
592        {
593            throw new AmetysRepositoryException("Error retrieving the custom property list.", e);
594        }
595    }
596    
597    /**
598     * Save the step.
599     */
600    public void save()
601    {
602        try
603        {
604            _node.getSession().save();
605        }
606        catch (RepositoryException e)
607        {
608            throw new AmetysRepositoryException("Error saving the .", e);
609        }
610    }
611    
612    /**
613     * Get a single-valued property.
614     * @param property the JCR property.
615     * @return the property value.
616     * @throws RepositoryException if an error occurs.
617     */
618    protected Object getSingleValuedProperty(Property property) throws RepositoryException
619    {
620        Object value;
621        
622        switch (property.getType())
623        {
624            case PropertyType.BOOLEAN:
625                value = property.getBoolean();
626                break;
627            case PropertyType.DATE:
628                value = property.getDate().getTime();
629                break;
630            case PropertyType.LONG:
631                value = property.getLong();
632                break;
633            default:
634                value = property.getString();
635                break;
636        }
637        
638        return value;
639    }
640    
641    /**
642     * Get a multi-valued property.
643     * @param property the JCR property.
644     * @return the property value (as an array).
645     * @throws RepositoryException if an error occurs.
646     */
647    protected Object getMultiValuedProperty(Property property) throws RepositoryException
648    {
649        Object value;
650        Value[] values = property.getValues();
651        
652        switch (property.getType())
653        {
654            case PropertyType.BOOLEAN:
655                boolean[] booleanResults = new boolean[values.length];
656                
657                for (int i = 0; i < values.length; i++)
658                {
659                    booleanResults[i] = values[i].getBoolean();
660                }
661                
662                value = booleanResults;
663                break;
664            case PropertyType.DATE:
665                Date[] dateResults = new Date[values.length];
666                
667                for (int i = 0; i < values.length; i++)
668                {
669                    dateResults[i] = values[i].getDate().getTime();
670                }
671                
672                value = dateResults;
673                break;
674            case PropertyType.LONG:
675                long[] longResults = new long[values.length];
676                
677                for (int i = 0; i < values.length; i++)
678                {
679                    longResults[i] = values[i].getLong();
680                }
681                
682                value = longResults;
683                break;
684            default:
685                String[] stringResults = new String[values.length];
686                
687                for (int i = 0; i < values.length; i++)
688                {
689                    stringResults[i] = values[i].getString();
690                }
691                
692                value = stringResults;
693                break;
694        }
695        
696        return value;
697    }
698    
699    @Override
700    public String toString()
701    {
702        return "AmetysStep (" + getId() + ")";
703    }
704    
705}