001/*
002 * Copyright (c) 2008-2010 The Regents of the University of California.
003 * All rights reserved.
004 *
005 * '$Author: crawl $'
006 * '$Date: 2015-04-03 22:12:00 +0000 (Fri, 03 Apr 2015) $' 
007 * '$Revision: 33311 $'
008 * 
009 * Permission is hereby granted, without written agreement and without
010 * license or royalty fees, to use, copy, modify, and distribute this
011 * software and its documentation for any purpose, provided that the above
012 * copyright notice and the following two paragraphs appear in all copies
013 * of this software.
014 *
015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
019 * SUCH DAMAGE.
020 *
021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
026 * ENHANCEMENTS, OR MODIFICATIONS.
027 *
028 */
029
030package org.kepler.provenance.sql;
031
032import java.sql.PreparedStatement;
033import java.sql.ResultSet;
034import java.sql.SQLException;
035import java.sql.Timestamp;
036import java.util.Date;
037import java.util.Stack;
038
039import org.kepler.provenance.RecordingException;
040import org.kepler.util.sql.Schema;
041
042import ptolemy.actor.LocalClock;
043import ptolemy.actor.gui.ColorAttribute;
044import ptolemy.actor.gui.SizeAttribute;
045import ptolemy.actor.gui.WindowPropertiesAttribute;
046import ptolemy.actor.gui.style.LineStyle;
047import ptolemy.actor.gui.style.TextStyle;
048import ptolemy.actor.parameters.PortParameter;
049import ptolemy.kernel.undo.UndoStackAttribute;
050import ptolemy.kernel.util.ConfigurableAttribute;
051import ptolemy.kernel.util.Location;
052import ptolemy.kernel.util.NamedObj;
053import ptolemy.kernel.util.Settable;
054import ptolemy.vergil.basic.KeplerDocumentationAttribute;
055import ptolemy.vergil.icon.BoxedValueIcon;
056import ptolemy.vergil.icon.EditIconTableau;
057import ptolemy.vergil.icon.EditorIcon;
058import ptolemy.vergil.icon.TextIcon;
059import ptolemy.vergil.icon.UpdatedValueIcon;
060import ptolemy.vergil.icon.ValueIcon;
061import ptolemy.vergil.icon.XMLIcon;
062
063
064/** SQL Implementation of Recording using SDM SPA schema.
065 *
066 * This uses version 7 of the schema. The difference between the 
067 * previous version is tracking changes in the workflow structure.
068 *
069 * @author Daniel Crawl
070 * @version $Id: SQLRecordingV7.java 33311 2015-04-03 22:12:00Z crawl $
071 *
072 */
073    
074public class SQLRecordingV7 extends SQLRecording //Recording
075{
076
077    public SQLRecordingV7() throws RecordingException
078    {
079        super();
080
081        //_debug("sql recording constructor");
082        
083        _workflowChangeTimeStack = new Stack<Timestamp>();
084
085        _dbReset();
086    }
087
088    ////////////////////////////////////////////////////////////////////////
089    //// Specification interface                                        ////
090    
091    /** Called before registering workflow contents. */
092    @Override
093    public void specificationStart() throws RecordingException
094    {
095        super.specificationStart();
096
097        _possibleWorkflowChangeStart();
098    }
099
100    /** Called when finished registering workflow contents. */
101    @Override
102    public void specificationStop() throws RecordingException
103    {
104        super.specificationStop(); 
105
106        _possibleWorkflowChangeStop();
107    }
108
109    /** Register a parameter. A parameter can be any <b>entity</b>
110     * stored in the MoML that does not have its own
111     * <code>regNNN()</code> method. This can be user-level 
112     * parameters (e.g., Parameter, StringParameter, etc.) or
113     * internal to Kepler (e.g., _location, semanticType000, etc.).
114     * (A "parameter" corresponds to a property in the MoML).
115     *
116     */
117    @Override
118    public boolean regParameter(NamedObj parameter) throws RecordingException
119    {
120        boolean ignore = false;
121
122        String name = parameter.getName();
123
124        if(parameter instanceof WindowPropertiesAttribute ||
125            parameter instanceof ConfigurableAttribute ||
126            parameter instanceof Location ||
127            parameter instanceof KeplerDocumentationAttribute ||
128            parameter instanceof SizeAttribute ||
129            parameter instanceof ColorAttribute ||
130            parameter instanceof TextIcon ||
131            parameter instanceof EditIconTableau.Factory ||
132            parameter instanceof TextStyle ||
133            parameter instanceof ValueIcon ||
134            parameter instanceof XMLIcon ||
135            parameter instanceof LineStyle ||
136            parameter instanceof UndoStackAttribute ||
137            parameter instanceof BoxedValueIcon ||
138            parameter instanceof UpdatedValueIcon  ||
139            parameter instanceof EditorIcon ||
140            parameter instanceof org.kepler.moml.NamedObjIdReferralList ||
141            parameter instanceof LocalClock ||
142            name.equals("bold") ||
143            name.equals("italic") ||
144            name.equals("fontFamily") ||
145            name.equals("textSize") ||
146            name.equals("_notDraggable") ||
147            name.indexOf("_vergil") == 0 ||
148            name.indexOf("_hide") == 0 ||
149            name.equals("_showName"))
150        {
151            ignore = true;
152        }
153        else
154        {
155            RegEntity re;
156            String fullNameStr = parameter.getFullName();
157            RegEntity.EntityType type = null;
158
159            // if it's a PortParameter, we'll need to register both the
160            // port and parameter.
161            if(parameter instanceof PortParameter)
162            {
163                type = RegEntity.EntityType.PortParameter;
164            }
165            else
166            {
167                type = RegEntity.EntityType.Parameter;
168            }
169            re = _checkEntity(parameter, type);
170
171
172            // see if the parameter has been recorded
173            if(re.isNew())
174            {
175                // register the parameter
176                _regParameterReal(parameter, re);
177
178                // if a port parameter, register the port
179                if(type == RegEntity.EntityType.PortParameter)
180                {
181                    _regPortReal(((PortParameter)parameter).getPort(), re);
182                }
183            }
184            // see if the parameter has a value
185            else if(parameter instanceof Settable)
186            {
187                String value = ((Settable)parameter).getValueAsString();
188                
189                try
190                {
191                    int id = re.getId();
192
193                    // see if paramater's value has changed
194                    if(_isChangedParam(id, value))
195                    {
196                        //_debug("SQL changed param: " + fullNameStr +
197                        //    " new value =" + value);
198                      
199                        String changedName = _changeEntityFullName(fullNameStr);
200
201                        // add a new row in the entity table with a new id
202                        RegEntity newRe = _addEntity(re.getContainerId(),
203                            type, changedName, parameter.getDisplayName(),
204                            id);
205
206                        // add a new row in the parameter table with the new value
207                        _regParameterReal(parameter, newRe);
208
209                        // if the parameter is a port parameter, we need to
210                        // create a new row in the port table with the new 
211                        // id. the new id might be used by new rows in the
212                        // token_flow table.
213                        if(parameter instanceof PortParameter)
214                        {
215                            _regPortReal(((PortParameter)parameter).getPort(),
216                                newRe);
217                        }
218                        
219                        // update the entity cache table with the new RegEntity
220                        // since it has the new id.
221                        _entityCacheTable.put(parameter, newRe);
222                    }
223                }
224                catch(SQLException e)
225                {
226                        _errorReset();
227                    throw new RecordingException("SQL ERROR: " + e.getMessage(), e);
228                }
229            }
230        }
231
232        return (! ignore);
233    }
234
235    ////////////////////////////////////////////////////////////////////////
236    //// Evolution interface                                            ////
237  
238    /** Start an evolution. */
239    @Override
240    public void evolutionStart() throws RecordingException
241    {
242        super.evolutionStart();
243
244        _possibleWorkflowChangeStart();
245    }
246
247    /** Stop an evolution. */
248    @Override
249    public void evolutionStop() throws RecordingException
250    {
251        super.evolutionStop();
252        
253        _possibleWorkflowChangeStop();
254    }
255
256    ////////////////////////////////////////////////////////////////////////
257    //// protected methods                                              ////
258   
259    /** Add a new row to the entity table. */
260    @Override
261    protected RegEntity _addEntity(int containerId, RegEntity.EntityType type,
262        String fullName, String displayName, int prevId)
263        throws RecordingException, SQLException
264    {
265        // see if we've recording the workflow change
266        if(_evolId == RegEntity.UNKNOWN_ID)
267        {
268            _addWorkflowChange();
269        }
270        
271        String typeStr = type.toString();
272
273        //(wf_change_id, container_id, type, name, prev_id, wf_id) " + 
274        _psEntityInsert.setInt(1, _evolId);
275        _psEntityInsert.setInt(2, containerId);
276        _psEntityInsert.setString(3, typeStr);
277        _psEntityInsert.setString(4, fullName);
278        _psEntityInsert.setInt(5, prevId);
279        _psEntityInsert.setInt(6, _wfId);
280        int newId = _dbType.insert(_psEntityInsert, "entity", "id");
281        RegEntity retval =  new RegEntity(newId, true, containerId, type);
282
283        // cache the new entry
284        //_entityCacheTable.put(fullName, retval);
285
286        if(_debugWriter != null)
287        {
288            _debugWrite("INSERT INTO ENTITY (" + _evolId + ", " +
289                containerId + ", " + type + ", " + fullName + ", " + prevId +
290                ", " + _wfId + ")");
291        }
292
293        return retval;
294    }
295
296    /** Add a new row to the workflow table. */
297    @Override
298    protected void _addWorkflow() throws RecordingException
299    {
300        try
301        {
302            synchronized(_psWorkflowInsert)
303            {
304                _psWorkflowInsert.setString(1, _wfNameStr);
305                _wfId = _dbType.insert(_psWorkflowInsert, "workflow", "id");
306                _wfReset();
307            }
308
309            if(_debugWriter != null)
310            {
311                _debugWrite("INSERT INTO WORKFLOW(" + _wfNameStr + ")");
312            }
313        }
314        catch(SQLException e)
315        {
316            throw new RecordingException("Error adding row to workflow:", e);        
317        }
318    }
319
320    /** Create a new row in the workflow_change table. */
321    protected void _addWorkflowChange() throws RecordingException
322    {
323        if(_wfUserStr == null)
324        {
325            throw new RecordingException("Need workflow user name");
326        }
327        
328        try
329        {
330            synchronized(_psWorkflowChangeInsert)
331            {
332                _psWorkflowChangeInsert.setString(1, _wfUserStr);
333                _psWorkflowChangeInsert.setTimestamp(2, 
334                    _workflowChangeTimeStack.peek());
335                _psWorkflowChangeInsert.setInt(3, _wfId);
336                _evolId = _dbType.insert(_psWorkflowChangeInsert,
337                    "workflow_change", "id");
338
339                if(_debugWriter != null)
340                {
341                    _debugWrite("INSERT INTO WORKFLOW_CHANGE(" + 
342                        _wfUserStr + ", wfChangeTime, " + _wfId + ")");
343                }
344            }
345        }
346        catch(SQLException e)
347        {
348            throw new RecordingException("Error adding to workflow_change: ",
349                e);
350        }
351    }
352
353    /** Initialize the prepared statements. */
354    @Override
355    protected void _createPreparedStatements() throws SQLException
356    {
357        if(_psWorkflowInsert == null && _schema.containsTable("workflow"))
358        {
359            _psWorkflowInsert = _dbType.getSQLInsert("workflow", "id", "name",
360                "?");
361        }
362
363        if(_psWorkflowChangeInsert == null &&
364            _schema.containsTable("workflow_change"))
365        {
366            _psWorkflowChangeInsert = _dbType.getSQLInsert("workflow_change",
367                "id", "user, time, wf_id", "?, ?, ?");
368        }
369
370        if(_psEntityInsert == null && _schema.containsTable("entity"))
371        {
372            _psEntityInsert = _dbType.getSQLInsert("entity", "id",
373                "wf_change_id, container_id, type, name, prev_id, wf_id",
374                "?, ?, ?, ?, ?, ?");
375        }
376
377        if(_psEntityQuery == null && _schema.containsTable("entity") &&
378            _schema.containsTable("workflow_change"))
379        {
380            String entityName = _dbType.getTableName("entity");
381            String wfChangeName = _dbType.getTableName("workflow_change");
382            String queryStr = "SELECT * FROM " +
383                    entityName + " e , " + wfChangeName + " w " +
384                    " WHERE e.wf_change_id = w.id AND w.wf_id = ? AND " +
385                    "e.name = ? and e.type = ? ORDER BY e.id DESC";
386                    // NOTE: the following is not supported by hsql
387                    //"entity.name = ? ORDER BY entity.id DESC LIMIT 1";
388            _psEntityQuery = _dbType.getPrepStatement(queryStr);
389        }
390
391        if(_psParameterValueQuery == null && _schema.containsTable("parameter"))
392        {
393            _psParameterValueQuery = _dbType.getSQLSelect("parameter", "value",
394                "id = ?");
395        }
396
397        if(_psWorkflowExecStart == null &&
398            _schema.containsTable("workflow_exec"))
399        {
400            String defaultTimeStr = _dbType.getDefaultTimeStr();
401
402            // workflow_id -> wf_id
403            _psWorkflowExecStart = _dbType.getSQLInsert("workflow_exec", "id",
404                "wf_id, user, start_time, end_time", "?, ?, ?, " +
405                defaultTimeStr);
406        }
407        
408        // create the remainder prepared statements.
409        super._createPreparedStatements();
410    }
411    
412    /** Create a Schema to reflect the v7 schema. */
413    @Override
414    protected Schema _createSchema()
415    {
416        return Schemas.createSchemaV7();
417    }
418
419    /** Reset when we encounter an error. */
420    @Override
421    protected void _errorReset() throws RecordingException
422    {
423        super._errorReset();
424        _workflowChangeTimeStack.clear();
425    }
426    
427    /** Set our prepared statements to null. */
428    @Override
429    protected void _nullPreparedStatements()
430    {
431        super._nullPreparedStatements();
432
433        _psWorkflowChangeInsert = null;
434        _psParameterValueQuery = null;
435    }
436
437    /** Reset when we use a different workflow. */
438    @Override
439    protected void _wfReset()
440    {
441        super._wfReset();
442        _evolId = RegEntity.UNKNOWN_ID;
443        //System.out.println("reset evolId");
444    }
445
446    ////////////////////////////////////////////////////////////////////////
447    //// protected variables                                            ////
448
449    protected PreparedStatement _psWorkflowChangeInsert;
450    protected PreparedStatement _psParameterValueQuery;
451
452    /** Timestamp of current change to workflow structure. */
453    protected Stack<Timestamp> _workflowChangeTimeStack;
454
455    /** ID of current change to workflow structure. */
456    protected int _evolId;
457
458    ////////////////////////////////////////////////////////////////////////
459    //// private methods                                                ////
460
461    /** See if a parameter changed its value. */
462    private boolean _isChangedParam(int id, String value)
463        throws RecordingException, SQLException
464    {
465        boolean retval = false;
466
467        synchronized(_psParameterValueQuery)
468        {
469            _psParameterValueQuery.setInt(1, id);
470            try(ResultSet rs = _psParameterValueQuery.executeQuery();) {
471
472                if(!rs.next())
473                {
474                    throw new RecordingException("ERROR: could not find " +
475                        "entity " + id + " in parameter table");
476                }
477    
478                String oldVal = rs.getString(1);
479    
480                // if the old value was null, see if the new value is not.
481                // otherwise, compare the values.
482                if((oldVal == null && value.length() > 0) ||
483                    (oldVal != null && !oldVal.equals(value)))
484                {
485                    retval = true;
486                }
487            }
488        }
489        return retval;
490    }
491
492    /** A workflow change may be starting. */
493    protected void _possibleWorkflowChangeStart() throws RecordingException
494    {
495        // save the time
496        _workflowChangeTimeStack.push(new Timestamp(new Date().getTime()));
497    }
498
499    /** A possible workflow change is stopping. */
500    protected void _possibleWorkflowChangeStop() throws RecordingException
501    {
502        // make sure change has started.
503        Timestamp curChangeTime = _workflowChangeTimeStack.pop();
504        if(curChangeTime == null)
505        {
506            throw new RecordingException("ERROR: workflow change not in " +
507                "progress.");
508        }
509
510        _evolId = RegEntity.UNKNOWN_ID; 
511    }
512}