View Javadoc

1   package org.lsst.ccs.config;
2   
3   import org.hibernate.annotations.IndexColumn;
4   import org.lsst.gruth.utils.Tracer;
5   import org.lsst.gruth.jutils.ComponentNode;
6   import org.lsst.gruth.jutils.HollowParm;
7   import org.lsst.gruth.jutils.NamedRefParm;
8   import org.lsst.gruth.nodes.ComponentFactory;
9   
10  import javax.persistence.*;
11  import java.io.PrintWriter;
12  import java.io.Serializable;
13  import java.util.*;
14  
15  /**
16   * Description for a subsystem as saved in database.
17   * The "subsystemName" and "tags" are keys (there is only one active subsystem with that name and tag, others
18   * are in historical data).
19   */
20  @MappedSuperclass
21  public abstract class SubsystemDescription implements Serializable {
22  
23      /**
24       * first time the description was valid : initially populated by the database
25       * (but copies can get it from an original: it depends on the purpose of the copy )
26       */
27      private long startTimestamp;//generated
28      /**
29       * valid limit. defaults to eternity except when the object is pushed in history.
30       */
31      private long endTimestamp = PackCst.STILL_VALID;
32      /**
33       * name of subsystem
34       */
35      private /*@NonNull*/ String subsystemName;
36      /**
37       * tag such as 'high wind'
38       */
39      private /*@NonNull*/ String tag = "";
40      /**
41       * name of user that created this
42       */
43      private /*@NonNull*/ String user;
44  
45      /**
46       * version
47       */
48      private /*@NonNull*/ String version;
49      /**
50       * Big object! see DescriptionType enum ...
51       * but not necessarily that big (so simply serializable Basic could be ok)
52       */
53      @Lob
54      protected /*@NonNull*/ Serializable configurationData;
55      /**
56       * <p/>
57       * TODO: only object_tree in future releases (other formats can be provided to constructors)
58       */
59      @Enumerated(EnumType.STRING)
60      protected  /*@NonNull*/ DataFlavour dataFlavour;
61      /**
62       * for which deployment  is this description valid?
63       */
64      @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
65      //@OneToMany(cascade = CascadeType.ALL)
66      @IndexColumn(name = "id", base = 1)
67      private /*@NonNull*/ List<DeploymentDescriptor> deployDescriptors =
68              // just in case! not ideal when handled as a bean
69              new ArrayList<DeploymentDescriptor>();
70  
71      /**
72       * transient object to access the descriptor list
73       */
74      @Transient
75      List<DeploymentDescriptor> unModifiableDescriptorList = Collections.unmodifiableList(deployDescriptors);
76  
77      // comments?
78  
79      /**
80       * a link to the description the current object may replace
81       */
82      private long previousDescriptionID;
83  
84      ///////////////////////// CONSTRUCTORS
85  
86  
87      /**
88       * for bean  convention: not public!
89       */
90      SubsystemDescription() {
91      }
92  
93      /**
94       * used by subclasses
95       *
96       * @param subsystemName     should not be null or empty
97       * @param tag               may be null or empty
98       * @param user              user that "owns" the description
99       * @param version           the version
100      * @param configurationData see DataFlavour documentation
101      * @param dataFlavour       which type of Configuration data
102      */
103     protected SubsystemDescription(String subsystemName, String tag, String user, String version, Serializable configurationData, DataFlavour dataFlavour) {
104         setSubsystemName(subsystemName);
105         setTag(tag);
106         //TODO: precondition configuration data should not be null
107         if (configurationData == null) {
108             throw new IllegalArgumentException("null configuration data");
109         }
110         this.user = user;
111         this.version = version;
112         switch (dataFlavour) {
113             case TREE_FROM_SOURCE:
114                 if (configurationData instanceof TextAndNode) {
115                     this.configurationData = configurationData;
116                 } else {
117                     this.configurationData = new TextAndNode(configurationData);
118                 }
119                 break;
120             case DUMMY_TEXT:
121             case PURE_OBJECT_TREE:
122                 this.configurationData = configurationData;
123                 break;
124         }
125         this.dataFlavour = dataFlavour;
126     }
127     //detach method ? copy constructor
128 
129     /**
130      * creates a copy of the SubsystemDescription that is not (yet) registered to the database.
131      * Subclasses <B>must</B> fill the corresponding paramDescriptions set.
132      *
133      * @param other
134      */
135     protected SubsystemDescription(SubsystemDescription other) {
136         this(other.getSubsystemName(), other.getTag(), other.getUser(),
137                 other.getVersion(), other.getConfigurationData(), other.getDataFlavour());
138         //copies the DeploymentDescriptors
139         deployDescriptors.addAll(other.getDeployDescriptors());
140         // danger: abstract method called in  constructor!
141     }
142 
143     ///////////////////////////// ACCESSORS/MUTATORS
144 
145     /**
146      * the technical id: zero if the object is not yet registered in database
147      *
148      * @return
149      */
150     public abstract long getId();
151 
152     /**
153      * used only by reconstruction code
154      *
155      * @param id
156      */
157     abstract void setId(long id);
158 
159     /**
160      * Detailed description of parameters that can be changed
161      *
162      * @return
163      */
164     public abstract Set<? extends ParameterDescription> getParamDescriptionSet();
165 
166     /**
167      * tells if the modifying methods can be invoked on a newly created objects.
168      *
169      * @return false if the object has been already registered in the database
170      */
171     public boolean isReadOnly() {
172         return getId() != 0L;
173     }
174 
175     public String getSubsystemName() {
176         return subsystemName;
177     }
178 
179     void setSubsystemName(String subsystemName) {
180         if (subsystemName == null) {
181             throw new IllegalArgumentException("null subsystemName");
182         }
183         this.subsystemName = subsystemName;
184     }
185 
186     public String getTag() {
187         return tag;
188     }
189 
190     void setTag(String tag) {
191         if (tag == null) tag = "";
192         this.tag = tag;
193     }
194 
195     public long getStartTimestamp() {
196         return startTimestamp;
197     }
198 
199     void setStartTimestamp(long startTimestamp) {
200         this.startTimestamp = startTimestamp;
201     }
202 
203     public long getEndTimestamp() {
204         return endTimestamp;
205     }
206 
207     void setEndTimestamp(long endTimestamp) {
208         this.endTimestamp = endTimestamp;
209     }
210 
211 
212     public String getUser() {
213         return user;
214     }
215 
216     void setUser(String user) {
217         this.user = user;
218     }
219 
220     public String getVersion() {
221         return version;
222     }
223 
224     void setVersion(String version) {
225         this.version = version;
226     }
227 
228     public Serializable getConfigurationData() {
229         return configurationData;
230     }
231 
232     void setConfigurationData(Serializable configurationData) {
233         this.configurationData = configurationData;
234     }
235 
236     public DataFlavour getDataFlavour() {
237         return dataFlavour;
238     }
239 
240     void setDataFlavour(DataFlavour dataFlavour) {
241         this.dataFlavour = dataFlavour;
242     }
243 
244     /**
245      * get the id of the previous subsystemDescription with same Name and tag.
246      * This data is modified by the configuration facade (when replacing a subsystemDescription)
247      *
248      * @return 0L if there is none
249      */
250     public long getPreviousDescriptionID() {
251         return previousDescriptionID;
252     }
253 
254     void setPreviousDescriptionID(long previousDescriptionID) {
255         this.previousDescriptionID = previousDescriptionID;
256     }
257 
258     /**
259      * The list of deployment descriptors is modifiable only for objects
260      * not yet registered in database. use addDeploymentDescriptor and
261      * removeDeploymentDescriptors to modify the list whil the object is not registered.
262      *
263      * @return an unmmodifiable view of the Deployment Descriptor list
264      */
265     public List<DeploymentDescriptor> getDeployDescriptors() {
266         return unModifiableDescriptorList;
267     }
268 
269     void setDeployDescriptors(List<DeploymentDescriptor> deploymentDescriptors) {
270         this.deployDescriptors = deploymentDescriptors;
271         unModifiableDescriptorList = Collections.unmodifiableList(deploymentDescriptors);
272     }
273 
274     /**
275      * adds a list of Deployment Descriptors
276      *
277      * @param descriptors
278      * @throws ImmutableStateException if called on an object registered on the database
279      */
280     public void addDeploymentDescriptors(DeploymentDescriptor... descriptors) {
281         if (isReadOnly()) {
282             throw new ImmutableStateException("Deployment descriptor list");
283         }
284         for (DeploymentDescriptor descriptor : descriptors) {
285             this.deployDescriptors.add(descriptor);
286         }
287     }
288 
289     /**
290      * removes a list of Deployment Descriptors
291      *
292      * @param descriptors
293      * @throws ImmutableStateException if called on an object registered on the database
294      */
295     public void removeDeploymentDescriptors(DeploymentDescriptor... descriptors) {
296         if (isReadOnly()) {
297             throw new ImmutableStateException("Deployment descriptor list");
298         }
299         for (DeploymentDescriptor descriptor : descriptors) {
300             this.deployDescriptors.remove(descriptor);
301         }
302     }
303 
304     /* problems with mapping on superClass
305     public Set<ParameterDescription> getParamDescriptions() {
306         return paramDescriptions;
307     }
308 
309      void setParamDescriptions(Set<ParameterDescription> paramDescriptions) {
310         this.paramDescriptions = paramDescriptions;
311     }
312     */
313 
314     ///////////////////////////////// IDENT METHODS
315 
316 
317     /**
318      * compares only the name and tag of subsystems not their content!
319      *
320      * @param
321      * @return
322      */
323     @Override
324     public boolean equals(Object o) {
325         if (this == o) return true;
326         if (!(o instanceof SubsystemDescription)) return false;
327 
328         SubsystemDescription that = (SubsystemDescription) o;
329 
330         if (getId() != that.getId()) return false;
331         if (!getSubsystemName().equals(that.getSubsystemName())) return false;
332         String tag = getTag();
333         if (tag != null ? !tag.equals(that.getTag()) : that.getTag() != null) return false;
334 
335         return true;
336     }
337 
338     @Override
339     public int hashCode() {
340         int result = getSubsystemName().hashCode();
341         String tag = getTag();
342         result = 31 * result + (tag != null ? tag.hashCode() : 0);
343         long id = getId();
344         result = 31 * result + (int) (id ^ (id >>> 32));
345         return result;
346     }
347 
348     @Override
349     public String toString() {
350         return "{" +
351                 "id=" + getId() +
352                 ";descriptions=" + this.getParamDescriptionSet() +
353                 '}';
354     }
355 //////////////////////////////  METHODS
356 
357 
358     /**
359      * Any <TT>ParameterDescription</TT> can be queried using any other <TT>PathObject</TT>
360      *
361      * @param path
362      * @return
363      */
364     public ParameterDescription fetch(PathObject path) {
365         for (ParameterDescription description : this.getParamDescriptionSet()) {
366             if (description.getPath().equals(path.getPath())) {
367                 return description;
368             }
369         }
370         return null;
371     }
372 
373     /**
374      * adds a list of parameter descriptions
375      *
376      * @param descriptions
377      * @throws ImmutableStateException if called on an immutable object
378      */
379     public abstract void addParameterDescriptions(ParameterDescription... descriptions);
380 
381     public abstract void addParameterDescriptions(Collection<ParameterDescription> descriptions);
382 
383     /**
384      * removes a list of parameter descriptions
385      *
386      * @param descriptions
387      * @throws ImmutableStateException if called on an immutable object
388      */
389     public abstract void removeParameterDescriptions(ParameterDescription... descriptions);
390 
391     ////////////////////////////////////// UTILITIES
392 
393     /**
394      * returns the base object structure that describes the subsystem description (as loaded from a text file).
395      * BEWARE: this data does not describe precisely <TT>ParameterDescriptions</TT> that may have been edited
396      * between creation and this request.
397      *
398      * @return
399      */
400     public ComponentNode getTopComponentNode() {
401         ComponentNode top;
402         switch (this.dataFlavour) {
403             case TREE_FROM_SOURCE:
404                 top = ((TextAndNode) this.configurationData).getComponentNode();
405                 break;
406             case PURE_OBJECT_TREE:
407                 if (!(this.configurationData instanceof ComponentNode)) {
408                     throw new IllegalArgumentException("Object data not a ComponentNode");
409                 }
410                 top = (ComponentNode) this.configurationData;
411                 break;
412             default:
413                 throw new UnsupportedOperationException(dataFlavour + " not supported yet!");
414 
415         }
416         return top;
417     }
418 
419 
420     /**
421      * checks if a real subsystem can be built from the configurationData: all objects in the
422      * component node are built but the subsystem at the top is not started.
423      *
424      * @return a built subsystem
425      * @throws Exception if anything goes wrong (classes not found, improper build, etc.)
426      */
427     public ComponentNode check() throws Exception {
428         ComponentNode top = getTopComponentNode();
429         checkNodeTree(top);
430         //TODO: change to CCSComponentFactory
431         ComponentFactory factory = new ComponentFactory(top);
432         return factory.buildObjectTreeFrom(top);
433     }
434 
435     private void checkNodeTree(ComponentNode top) {
436         // added pre-check for constraints
437         Collection<ParameterDescription> parmDesc = parameterDescriptionsFromNode(top, DEFAULT_TREE_PARAMETER_FILTER, PackCst.DESIGNER_LEVEL);
438         for (ParameterDescription parameterDescription : parmDesc) {
439             Object realValue = Constraints.check(parameterDescription.getTypeName(), parameterDescription.getDefaultValue(), parameterDescription.getConstraints());
440         }
441         ArrayList<ComponentNode> children = top.getChildren();
442         if (children != null) {
443             for (ComponentNode child : children) {
444                 checkNodeTree(child);
445             }
446         }
447     }
448 
449     /**
450      * writes to a property file properties that can be edited to generate a ConfigProfile
451      *
452      * @param writer
453      * @param levelMax
454      */
455     public void generateConfigProperties(PrintWriter writer, int levelMax) {
456         List<ParameterDescription> list = new ArrayList<ParameterDescription>(getParamDescriptionSet());
457         Collections.sort(list, PathObject.COMPARATOR);
458         for (ParameterDescription description : list) {
459             if (description.getLevel() <= levelMax) {
460                 writer.println(description.toPropertyString());
461             }
462         }
463     }
464 
465 
466     /**
467      * to be used to create startup data object from a non-registered Configuration data (in a properties file).
468      * use at your own risk!
469      *
470      * @param configProps
471      * @return
472      * @throws IllegalStateException if the set of ParameterDescription is not populated
473      *                               Runtime Exception if illegal modification try (type or immutable object)
474      */
475     public ComponentNode getModifiedConfigurationData(Properties configProps) throws RuntimeException {
476         ComponentNode res = getTopComponentNode().clone();
477         Set<? extends ParameterDescription> paramDescriptions = this.getParamDescriptionSet();
478         if (paramDescriptions.size() == 0) {
479             throw new IllegalStateException("Description with no parametersDescription: populate first");
480         }
481         for (ParameterDescription parameterDescription : paramDescriptions) {
482             ParameterPath path = parameterDescription.getPath();
483             String pathName = path.toString();
484             String value = configProps.getProperty(pathName);
485             if (value == null) { //try simpleName
486                 value = configProps.getProperty(parameterDescription.getSimpleName());
487                 if (value == null) continue;
488             }
489             // we have a String value
490             Object realValue = Constraints.check(parameterDescription.getTypeName(), value, parameterDescription.getConstraints());
491             String componentName = path.getComponentName();
492             String codeName = path.getCodeName();
493             if (codeName != null && !"".equals(codeName)) {
494                 throw new UnsupportedOperationException(" no change on methods yet --> " + codeName);
495             }
496             String parameterName = path.getParameterName();
497             ComponentNode goalComponent = (ComponentNode) res.getNodeByName(componentName);
498             // get Parameter
499             Object rawParm = goalComponent.getAttributes().get(parameterName);
500             // not null
501             if (rawParm instanceof HollowParm) {
502                 HollowParm hollow = (HollowParm) rawParm;
503                 hollow.modifyChecked(realValue);
504                 //System.out.println("modified :" + componentName + "//" +parameterName + " = " +realValue);
505                 Tracer.trace(Tracer.NODE_MODIF, "modified parameter ", componentName, "//",
506                         parameterName , ". new value=", realValue);
507             } else {
508                 throw new IllegalArgumentException("parameter not modifiable" + rawParm);
509             }
510         }
511         //System.out.println("modified node :" +res);
512         Tracer.trace(Tracer.NODE_MODIF, "modification of node ", res);
513         return res;
514     }
515 
516 
517     /**
518      * returns a Map that contains all ParameterDescription both by their pathName and their simpleName
519      *
520      * @return
521      */
522     public Map<String, ParameterDescription> generateDescriptionMap() {
523         Map<String, ParameterDescription> map = new HashMap<String, ParameterDescription>();
524         for (ParameterDescription description : getParamDescriptionSet()) {
525             map.put(description.getPath().toString(), description);
526             String simpleName = description.getSimpleName();
527             if (simpleName != null && !"".equals(simpleName.trim())) {
528                 map.put(simpleName.trim(), description);
529             }
530         }
531         return map;
532     }
533 
534     /**
535      * gets the base parameters from a description
536      *
537      * @param filter drops unwanted parameter bases
538      * @return
539      */
540     public Collection<ParameterBase> getBaseParameters(ParameterFilter filter) {
541         ComponentNode top = getTopComponentNode();
542         return getBaseParametersFromTree(top, filter);
543     }
544 
545     /**
546      * Default filter for parameters: gets rid of all parameters that are references to other components
547      * and considers that a parameter with name "name" is out.
548      */
549     public static final ParameterFilter DEFAULT_TREE_PARAMETER_FILTER = new ParameterFilter() {
550         @Override
551         public boolean filter(String parameterName, Object value) {
552             if ("name".equalsIgnoreCase(parameterName)) return false;
553             if (value instanceof NamedRefParm) return false;
554             return true;
555         }
556     };
557 
558     /**
559      * get the base parameters using the default filter.
560      *
561      * @return
562      */
563     public Collection<ParameterBase> getBaseParameters() {
564         ComponentNode top = getTopComponentNode();
565         return getBaseParametersFromTree(top, DEFAULT_TREE_PARAMETER_FILTER);
566     }
567 
568     /**
569      * creates a Collection of <TT>ParameterBase</TT> objects from "tree" data that represents
570      * a subsystem.
571      *
572      * @param filter
573      * @return
574      */
575     Collection<ParameterBase> getBaseParametersFromTree(ComponentNode top, ParameterFilter filter) {
576         ArrayList<ParameterBase> list = new ArrayList<ParameterBase>();
577         populateParameterBasesFromTop(list, top, filter);
578         return list;
579     }
580 
581     /**
582      * gets the <TT>ParameterBase</TT> objects that describes arguments passed to the constructor
583      * linked to a node of the configuration tree.
584      * <p/>
585      * this code should be changed to describe also methods' parameters.
586      * </P>
587      *
588      * @param node
589      * @param filter
590      * @return
591      */
592     public static Collection<ParameterBase> parametersFromNode(ComponentNode node, ParameterFilter filter) {
593         Collection<ParameterBase> res = new ArrayList<ParameterBase>();
594         Map attributes = node.getAttributes();
595         String nodeKey = node.getKey();
596         //TODO: handle positional parameters!!
597         //TODO: handle other methods (not only constructors)
598         if (attributes != null) {
599             Set<Map.Entry> keyVals = attributes.entrySet();
600             for (Map.Entry entry : keyVals) {
601                 Object val = entry.getValue();
602                 String parmName = (String) entry.getKey();
603                 if (!filter.filter(parmName, val)) {
604                     continue;
605                 }
606                 //TODO: handle structParm
607                 if (val instanceof HollowParm) {
608                     HollowParm hollow = (HollowParm) val;
609                     if (!hollow.isReadOnly()) {
610                         // first get the type
611                         String typeName = hollow.getValueClass().getName();
612                         ParameterBase base = new ParameterBase(nodeKey, "", parmName, typeName, hollow.toString());
613                         res.add(base);
614                     }
615                 }
616             }
617         }
618         return res;
619     }
620 
621     /**
622      * "tree-walker" to populate <TT>ParameterBase</TT> collection
623      *
624      * @param list
625      * @param topNode
626      * @param filter
627      */
628     public static void populateParameterBasesFromTop(Collection<ParameterBase> list, ComponentNode topNode,
629                                                      ParameterFilter filter) {
630         list.addAll(parametersFromNode(topNode, filter));
631         ArrayList<ComponentNode> children = topNode.getChildren();
632         if (children != null) {
633             for (ComponentNode childNode : children) {
634                 populateParameterBasesFromTop(list, childNode, filter);
635             }
636         }
637         //TODO: handle calls !
638     }
639 
640     ////////////////////////////// getting parameterDescription templates
641     ///////////// TODO: this code is almost a copy of the methods for ParameterBase : mutualize!
642     ////////////////////////////////////
643 
644     /**
645      * Loks like the <TT>getBaseParameters</TT> but builds a collection of <TT>ParameterDescription</TT>.
646      * The additional data may be empty if no additional data has been specified in the original text file/
647      *
648      * @param maxLevel
649      * @param filter
650      * @return
651      */
652     public Collection<ParameterDescription> getPossibleDescriptions(int maxLevel, ParameterFilter filter) {
653         ComponentNode top = getTopComponentNode();
654         ArrayList<ParameterDescription> list = new ArrayList<ParameterDescription>();
655         populateParameterDescriptionsFromTop(list, top, filter, maxLevel);
656         return list;
657     }
658 
659     public Collection<ParameterDescription> getPossibleDescriptions(int maxLevel) {
660         return getPossibleDescriptions(maxLevel, DEFAULT_TREE_PARAMETER_FILTER);
661     }
662 
663     public static void populateParameterDescriptionsFromTop(Collection<ParameterDescription> list,
664                                                             ComponentNode topNode, ParameterFilter filter, int maxLevel) {
665         list.addAll(parameterDescriptionsFromNode(topNode, filter, maxLevel));
666         ArrayList<ComponentNode> children = topNode.getChildren();
667         if (children != null) {
668             for (ComponentNode childNode : children) {
669                 populateParameterDescriptionsFromTop(list, childNode, filter, maxLevel);
670             }
671         }
672         //TODO: handle calls !
673 
674     }
675 
676     public static Collection<ParameterDescription> parameterDescriptionsFromNode(ComponentNode node,
677                                                                                  ParameterFilter filter,
678                                                                                  int maxLevel) {
679         Collection<ParameterDescription> res = new ArrayList<ParameterDescription>();
680         Map attributes = node.getAttributes();
681         String nodeKey = node.getKey();
682         //TODO: handle positional parameters!!
683         //TODO: handle other methods (not only constructors)
684         if (attributes != null) {
685             Set<Map.Entry> keyVals = attributes.entrySet();
686             for (Map.Entry entry : keyVals) {
687                 Object val = entry.getValue();
688                 String parmName = (String) entry.getKey();
689                 if (!filter.filter(parmName, val)) {
690                     continue;
691                 }
692                 //TODO: handle structParm
693                 if (val instanceof HollowParm) {
694                     HollowParm hollow = (HollowParm) val;
695                     if (!hollow.isReadOnly()) {
696                         // first get the type
697                         String typeName = hollow.getValueClass().getName();
698                         ParameterBase base = new ParameterBase(nodeKey, "", parmName, typeName, hollow.toString());
699                         String description = "";
700                         String simpleName = "";
701                         String constraints = "";
702                         boolean notModifiableAtRuntime = false ;
703                         int level = PackCst.DESIGNER_LEVEL;
704                         Properties props = hollow.getProperties();
705                         if (props != null) {
706                             // description
707                             description = props.getProperty("description", "");
708                             // simpleName
709                             simpleName = props.getProperty("simpleName", "");
710                             // constraints
711                             constraints = props.getProperty("constraints", "");
712                             // level
713                             String request = props.getProperty("level");
714                             if (request != null) {
715                                 level = Integer.parseInt(request);
716                             }
717                             String isStatic = props.getProperty("static") ;
718                             if(isStatic != null) {
719                                 notModifiableAtRuntime = Boolean.valueOf(isStatic) ;
720                             }
721                         }
722                         if (level <= maxLevel) {
723                             AParameterDescription parmDescription = new AParameterDescription(base, description, simpleName, constraints, level);
724                             parmDescription.setNotModifiableAtRuntime(notModifiableAtRuntime);
725                             res.add(parmDescription);
726                         }
727                     }
728                 }
729             }
730         }
731         return res;
732     }
733 
734 
735 }
736