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            return (int) _node.getProperty(AbstractJackrabbitWorkflowStore.__ACTION_ID_PROPERTY).getLong();
130        }
131        catch (RepositoryException e)
132        {
133            throw new AmetysRepositoryException("Error getting the action ID property.", e);
134        }
135    }
136    
137    /**
138     * Set the action ID.
139     * @param actionId the action ID to set.
140     */
141    public void setActionId(int actionId)
142    {
143        try
144        {
145            _node.setProperty(AbstractJackrabbitWorkflowStore.__ACTION_ID_PROPERTY, actionId);
146        }
147        catch (RepositoryException e)
148        {
149            throw new AmetysRepositoryException("Error setting the action ID property.", e);
150        }
151    }
152    
153    @Override
154    public String getCaller()
155    {
156        try
157        {
158            String caller = null;
159            
160            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__CALLER_PROPERTY))
161            {
162                caller = _node.getProperty(AbstractJackrabbitWorkflowStore.__CALLER_PROPERTY).getString();
163            }
164            
165            return caller;
166        }
167        catch (RepositoryException e)
168        {
169            throw new AmetysRepositoryException("Error getting the caller property.", e);
170        }
171    }
172    
173    /**
174     * Set the caller.
175     * @param caller the caller to set.
176     */
177    public void setCaller(String caller)
178    {
179        try
180        {
181            _node.setProperty(AbstractJackrabbitWorkflowStore.__CALLER_PROPERTY, caller);
182        }
183        catch (RepositoryException e)
184        {
185            throw new AmetysRepositoryException("Error setting the caller property.", e);
186        }
187    }
188    
189    @Override
190    public Date getStartDate()
191    {
192        try
193        {
194            Date value = null;
195            
196            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__START_DATE_PROPERTY))
197            {
198                value = _node.getProperty(AbstractJackrabbitWorkflowStore.__START_DATE_PROPERTY).getDate().getTime();
199            }
200            
201            return value;
202        }
203        catch (RepositoryException e)
204        {
205            throw new AmetysRepositoryException("Error getting the start date property.", e);
206        }
207    }
208    
209    /**
210     * Set the step start date.
211     * @param startDate the start date to set.
212     */
213    public void setStartDate(Date startDate)
214    {
215        try
216        {
217            GregorianCalendar cal = new GregorianCalendar();
218            cal.setTime(startDate);
219            
220            _node.setProperty(AbstractJackrabbitWorkflowStore.__START_DATE_PROPERTY, cal);
221        }
222        catch (RepositoryException e)
223        {
224            throw new AmetysRepositoryException("Error setting the start date property.", e);
225        }
226    }
227    
228    @Override
229    public Date getDueDate()
230    {
231        try
232        {
233            Date value = null;
234            
235            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__DUE_DATE_PROPERTY))
236            {
237                value = _node.getProperty(AbstractJackrabbitWorkflowStore.__DUE_DATE_PROPERTY).getDate().getTime();
238            }
239            
240            return value;
241        }
242        catch (RepositoryException e)
243        {
244            throw new AmetysRepositoryException("Error getting the due date property.", e);
245        }
246    }
247    
248    /**
249     * Set the step due date.
250     * @param dueDate the due date to set.
251     */
252    public void setDueDate(Date dueDate)
253    {
254        try
255        {
256            GregorianCalendar cal = new GregorianCalendar();
257            cal.setTime(dueDate);
258            
259            _node.setProperty(AbstractJackrabbitWorkflowStore.__DUE_DATE_PROPERTY, cal);
260        }
261        catch (RepositoryException e)
262        {
263            throw new AmetysRepositoryException("Error setting the due date property.", e);
264        }
265    }
266    
267    @Override
268    public Date getFinishDate()
269    {
270        try
271        {
272            Date value = null;
273            
274            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__FINISH_DATE_PROPERTY))
275            {
276                value = _node.getProperty(AbstractJackrabbitWorkflowStore.__FINISH_DATE_PROPERTY).getDate().getTime();
277            }
278            
279            return value;
280        }
281        catch (RepositoryException e)
282        {
283            throw new AmetysRepositoryException("Error getting the finish date property.", e);
284        }
285    }
286    
287    /**
288     * Set the step finish date.
289     * @param finishDate the finish date to set.
290     */
291    public void setFinishDate(Date finishDate)
292    {
293        try
294        {
295            GregorianCalendar cal = new GregorianCalendar();
296            cal.setTime(finishDate);
297            
298            _node.setProperty(AbstractJackrabbitWorkflowStore.__FINISH_DATE_PROPERTY, cal);
299        }
300        catch (RepositoryException e)
301        {
302            throw new AmetysRepositoryException("Error setting the finish date property.", e);
303        }
304    }
305    
306    @Override
307    public String getOwner()
308    {
309        try
310        {
311            String owner = null;
312            
313            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__OWNER_PROPERTY))
314            {
315                owner = _node.getProperty(AbstractJackrabbitWorkflowStore.__OWNER_PROPERTY).getString();
316            }
317            
318            return owner;
319        }
320        catch (RepositoryException e)
321        {
322            throw new AmetysRepositoryException("Error getting the owner property.", e);
323        }
324    }
325    
326    /**
327     * Set the owner.
328     * @param owner the owner to set.
329     */
330    public void setOwner(String owner)
331    {
332        try
333        {
334            _node.setProperty(AbstractJackrabbitWorkflowStore.__OWNER_PROPERTY, owner);
335        }
336        catch (RepositoryException e)
337        {
338            throw new AmetysRepositoryException("Error setting the owner property.", e);
339        }
340    }
341    
342    @Override
343    public long[] getPreviousStepIds()
344    {
345        try
346        {
347            List<Long> previousStepsIds = new ArrayList<>();
348            
349            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__PREVIOUS_STEPS_PROPERTY))
350            {
351                Value[] previousSteps = _node.getProperty(AbstractJackrabbitWorkflowStore.__PREVIOUS_STEPS_PROPERTY).getValues();
352    
353                for (Value previousStep : previousSteps)
354                {
355                    Node historyStep = _node.getSession().getNodeByIdentifier(previousStep.getString());
356    
357                    long previousStepId = historyStep.getProperty(AbstractJackrabbitWorkflowStore.__ID_PROPERTY).getLong();
358                    previousStepsIds.add(previousStepId);
359                }
360            }
361            
362            long[] previousIds = new long[previousStepsIds.size()];
363            
364            for (int i = 0; i < previousIds.length; i++)
365            {
366                previousIds[i] = previousStepsIds.get(i);
367            }
368            
369            return previousIds;
370        }
371        catch (RepositoryException e)
372        {
373            throw new AmetysRepositoryException("Error getting the previous IDs property.", e);
374        }
375    }
376    
377    /**
378     * Set the previous step IDs.
379     * @param previousStepIds the previous step IDs to set.
380     */
381    public void setPreviousStepIds(long[] previousStepIds)
382    {
383        try
384        {
385            Session session = _node.getSession();
386            ValueFactory valueFactory = session.getValueFactory();
387            
388            Value[] previousStepsRefs = new Value[0];
389            
390            if (previousStepIds != null)
391            {
392                previousStepsRefs = new Value[previousStepIds.length];
393                
394                for (int i = 0; i < previousStepIds.length; i++)
395                {
396                    long previousStepId = previousStepIds[i];
397                    
398                    // Retrieve existing step for this entry
399                    Node previousStep = _store.getHistoryStepNode(session, getEntryId(), previousStepId);
400                    
401                    // Use the ValueFactory to set a reference to this step
402                    previousStepsRefs[i] = valueFactory.createValue(previousStep);
403                }
404            }
405            
406            _node.setProperty(AbstractJackrabbitWorkflowStore.__PREVIOUS_STEPS_PROPERTY, previousStepsRefs);
407        }
408        catch (RepositoryException e)
409        {
410            throw new AmetysRepositoryException("Error setting the previous IDs property.", e);
411        }
412    }
413    
414    @Override
415    public String getStatus()
416    {
417        try
418        {
419            String status = null;
420            
421            if (_node.hasProperty(AbstractJackrabbitWorkflowStore.__STATUS_PROPERTY))
422            {
423                status = _node.getProperty(AbstractJackrabbitWorkflowStore.__STATUS_PROPERTY).getString();
424            }
425            
426            return status;
427        }
428        catch (RepositoryException e)
429        {
430            throw new AmetysRepositoryException("Error getting the status property.", e);
431        }
432    }
433    
434    /**
435     * Set the status.
436     * @param status the status to set.
437     */
438    public void setStatus(String status)
439    {
440        try
441        {
442            _node.setProperty(AbstractJackrabbitWorkflowStore.__STATUS_PROPERTY, status);
443        }
444        catch (RepositoryException e)
445        {
446            throw new AmetysRepositoryException("Error setting the status property.", e);
447        }
448    }
449    
450    /**
451     * Get a custom property value.
452     * @param name the property name.
453     * @return the custom property value.
454     */
455    public Object getProperty(String name)
456    {
457        try
458        {
459            Object value = null;
460            String qName = __CUSTOM_PROP_PREFIX + name;
461            
462            if (_node.hasProperty(qName))
463            {
464                Property property = _node.getProperty(qName);
465                if (property.getDefinition().isMultiple())
466                {
467                    value = getMultiValuedProperty(property);
468                }
469                else
470                {
471                    value = getSingleValuedProperty(property);
472                }
473            }
474            
475            return value;
476        }
477        catch (RepositoryException e)
478        {
479            throw new AmetysRepositoryException("Error setting the custom property: " + name, e);
480        }
481    }
482    
483    /**
484     * Set a custom property value.
485     * @param name the property name.
486     * @param value the value to set.
487     */
488    public void setProperty(String name, Object value)
489    {
490        try
491        {
492            ValueFactory valueFactory = _node.getSession().getValueFactory();
493            String qName = __CUSTOM_PROP_PREFIX + name;
494            
495            if (value instanceof Boolean)
496            {
497                _node.setProperty(qName, (Boolean) value);
498            }
499            else if (value instanceof boolean[])
500            {
501                boolean[] v = (boolean[]) value;
502                Value[] values = new Value[v.length];
503                for (int i = 0; i < v.length; i++)
504                {
505                    values[i] = valueFactory.createValue(v[i]);
506                }
507                
508                _node.setProperty(qName, values);
509            }
510            else if (value instanceof Date)
511            {
512                GregorianCalendar gc = new GregorianCalendar();
513                gc.setTime((Date) value);
514                _node.setProperty(qName, gc);
515            }
516            else if (value instanceof Date[])
517            {
518                Date[] v = (Date[]) value;
519                Value[] values = new Value[v.length];
520                for (int i = 0; i < v.length; i++)
521                {
522                    GregorianCalendar gc = new GregorianCalendar();
523                    gc.setTime(v[i]);
524                    values[i] = valueFactory.createValue(gc);
525                }
526                
527                _node.setProperty(qName, values);
528            }
529            else if (value instanceof Long)
530            {
531                _node.setProperty(qName, (Long) value);
532            }
533            else if (value instanceof long[])
534            {
535                long[] v = (long[]) value;
536                Value[] values = new Value[v.length];
537                for (int i = 0; i < v.length; i++)
538                {
539                    values[i] = valueFactory.createValue(v[i]);
540                }
541                
542                _node.setProperty(qName, values);
543            }
544            else if (value instanceof String[])
545            {
546                String[] v = (String[]) value;
547                Value[] values = new Value[v.length];
548                for (int i = 0; i < v.length; i++)
549                {
550                    values[i] = valueFactory.createValue(v[i]);
551                }
552                
553                _node.setProperty(qName, values);
554            }
555            else
556            {
557                _node.setProperty(qName, value.toString());
558            }
559        }
560        catch (RepositoryException e)
561        {
562            throw new AmetysRepositoryException("Error setting the custom property: " + name, e);
563        }
564    }
565    
566    /**
567     * Get all the custom property names.
568     * @return the custom property names.
569     */
570    public Set<String> getPropertyNames()
571    {
572        try
573        {
574            Set<String> propertyNames = new LinkedHashSet<>();
575            
576            PropertyIterator properties = _node.getProperties(__CUSTOM_PROP_PREFIX + "*");
577            while (properties.hasNext())
578            {
579                String qName = properties.nextProperty().getName();
580                String name = qName.substring(__CUSTOM_PROP_PREFIX.length());
581                propertyNames.add(name);
582            }
583            
584            return propertyNames;
585        }
586        catch (RepositoryException e)
587        {
588            throw new AmetysRepositoryException("Error retrieving the custom property list.", e);
589        }
590    }
591    
592    /**
593     * Save the step.
594     */
595    public void save()
596    {
597        try
598        {
599            _node.getSession().save();
600        }
601        catch (RepositoryException e)
602        {
603            throw new AmetysRepositoryException("Error saving the .", e);
604        }
605    }
606    
607    /**
608     * Get a single-valued property.
609     * @param property the JCR property.
610     * @return the property value.
611     * @throws RepositoryException if an error occurs.
612     */
613    protected Object getSingleValuedProperty(Property property) throws RepositoryException
614    {
615        Object value;
616        
617        switch (property.getType())
618        {
619            case PropertyType.BOOLEAN:
620                value = property.getBoolean();
621                break;
622            case PropertyType.DATE:
623                value = property.getDate().getTime();
624                break;
625            case PropertyType.LONG:
626                value = property.getLong();
627                break;
628            default:
629                value = property.getString();
630                break;
631        }
632        
633        return value;
634    }
635    
636    /**
637     * Get a multi-valued property.
638     * @param property the JCR property.
639     * @return the property value (as an array).
640     * @throws RepositoryException if an error occurs.
641     */
642    protected Object getMultiValuedProperty(Property property) throws RepositoryException
643    {
644        Object value;
645        Value[] values = property.getValues();
646        
647        switch (property.getType())
648        {
649            case PropertyType.BOOLEAN:
650                boolean[] booleanResults = new boolean[values.length];
651                
652                for (int i = 0; i < values.length; i++)
653                {
654                    booleanResults[i] = values[i].getBoolean();
655                }
656                
657                value = booleanResults;
658                break;
659            case PropertyType.DATE:
660                Date[] dateResults = new Date[values.length];
661                
662                for (int i = 0; i < values.length; i++)
663                {
664                    dateResults[i] = values[i].getDate().getTime();
665                }
666                
667                value = dateResults;
668                break;
669            case PropertyType.LONG:
670                long[] longResults = new long[values.length];
671                
672                for (int i = 0; i < values.length; i++)
673                {
674                    longResults[i] = values[i].getLong();
675                }
676                
677                value = longResults;
678                break;
679            default:
680                String[] stringResults = new String[values.length];
681                
682                for (int i = 0; i < values.length; i++)
683                {
684                    stringResults[i] = values[i].getString();
685                }
686                
687                value = stringResults;
688                break;
689        }
690        
691        return value;
692    }
693    
694    @Override
695    public String toString()
696    {
697        return "AmetysStep (" + getId() + ")";
698    }
699    
700}