76bc5151af66bc06c54a25fc26aadf3a5138edb7
[policy/drools-pdp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * policy-management
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.openecomp.policy.drools.controller.internal;
22
23 import java.util.ArrayList;
24 import java.util.List;
25
26 import org.apache.commons.collections4.queue.CircularFifoQueue;
27
28 import org.openecomp.policy.common.logging.eelf.MessageCodes;
29 import org.openecomp.policy.common.logging.flexlogger.FlexLogger;
30 import org.openecomp.policy.common.logging.flexlogger.Logger;
31 import org.openecomp.policy.drools.controller.DroolsController;
32 import org.openecomp.policy.drools.core.PolicyContainer;
33 import org.openecomp.policy.drools.core.PolicySession;
34 import org.openecomp.policy.drools.core.jmx.PdpJmx;
35 import org.openecomp.policy.drools.event.comm.TopicSink;
36 import org.openecomp.policy.drools.protocol.coders.EventProtocolCoder;
37 import org.openecomp.policy.drools.protocol.coders.JsonProtocolFilter;
38 import org.openecomp.policy.drools.protocol.coders.TopicCoderFilterConfiguration;
39 import org.openecomp.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomGsonCoder;
40 import org.openecomp.policy.drools.protocol.coders.TopicCoderFilterConfiguration.CustomJacksonCoder;
41 import org.openecomp.policy.drools.protocol.coders.TopicCoderFilterConfiguration.PotentialCoderFilter;
42 import org.openecomp.policy.drools.utils.ReflectionUtil;
43 import com.fasterxml.jackson.annotation.JsonIgnore;
44
45 /**
46  * Maven-based Drools Controller that interacts with the 
47  * policy-core PolicyContainer and PolicySession to manage
48  * Drools containers instantiated using Maven.
49  */
50 public class MavenDroolsController implements DroolsController {
51         
52         /**
53          * logger 
54          */
55         private static Logger  logger = FlexLogger.getLogger(MavenDroolsController.class);
56         
57         /**
58          * Policy Container, the access object to the policy-core layer
59          */
60         @JsonIgnore
61         protected final PolicyContainer policyContainer;
62         
63         /**
64          * alive status of this drools controller, 
65          * reflects invocation of start()/stop() only
66          */
67         protected volatile boolean alive = false;
68         
69         /**
70          * locked status of this drools controller,
71          * reflects if i/o drools related operations are permitted,
72          * more specifically: offer() and deliver().
73          * It does not affect the ability to start and stop 
74          * underlying drools infrastructure
75          */
76         protected volatile boolean locked = false;
77         
78         /**
79          * list of topics, each with associated decoder classes, each
80          * with a list of associated filters.
81          */
82         protected List<TopicCoderFilterConfiguration> decoderConfigurations;
83         
84         /**
85          * list of topics, each with associated encoder classes, each
86          * with a list of associated filters.
87          */
88         protected List<TopicCoderFilterConfiguration> encoderConfigurations;
89         
90         /**
91          * recent source events processed
92          */
93         protected final CircularFifoQueue<Object> recentSourceEvents = new CircularFifoQueue<Object>(10);
94         
95         /**
96          * recent sink events processed
97          */
98         protected final CircularFifoQueue<String> recentSinkEvents = new CircularFifoQueue<String>(10);
99         
100         /**
101          * original Drools Model/Rules classloader hash
102          */
103         protected int modelClassLoaderHash;
104
105         /**
106          * Expanded version of the constructor 
107          * 
108          * @param groupId maven group id
109          * @param artifactId maven artifact id
110          * @param version maven version
111          * @param decoderConfiguration list of topic -> decoders -> filters mapping
112          * @param encoderConfiguration list of topic -> encoders -> filters mapping
113          * 
114          * @throws IllegalArgumentException invalid arguments passed in
115          */
116         public MavenDroolsController(String groupId, 
117                                                                  String artifactId, 
118                                                                  String version,
119                                                                  List<TopicCoderFilterConfiguration> decoderConfigurations,
120                                                                  List<TopicCoderFilterConfiguration> encoderConfigurations) 
121                    throws IllegalArgumentException {
122                 
123                 if (logger.isInfoEnabled())
124                         logger.info("DROOLS CONTROLLER: instantiation " +  this + 
125                                             " -> {" + groupId + ":" + artifactId + ":" + version + "}");
126                 
127                 if (groupId == null || artifactId == null || version == null ||
128                         groupId.isEmpty() || artifactId.isEmpty() || version.isEmpty()) {
129                         throw new IllegalArgumentException("Missing maven coordinates: " + 
130                                                                    groupId + ":" + artifactId + ":" + 
131                                                                    version);
132                 }
133                 
134                 this.policyContainer= new PolicyContainer(groupId, artifactId, version);
135                 this.init(decoderConfigurations, encoderConfigurations);
136                 
137                 if (logger.isInfoEnabled())
138                         logger.info("DROOLS CONTROLLER: instantiation completed " +  this);
139         }
140         
141         /**
142          * init encoding/decoding configuration
143          * @param decoderConfiguration list of topic -> decoders -> filters mapping
144          * @param encoderConfiguration list of topic -> encoders -> filters mapping
145          */
146         protected void init(List<TopicCoderFilterConfiguration> decoderConfigurations,
147                                     List<TopicCoderFilterConfiguration> encoderConfigurations) {
148                 
149                 this.decoderConfigurations = decoderConfigurations;
150                 this.encoderConfigurations = encoderConfigurations;
151                 
152                 this.initCoders(decoderConfigurations, true);
153                 this.initCoders(encoderConfigurations, false);
154                 
155                 this.modelClassLoaderHash = this.policyContainer.getClassLoader().hashCode();           
156         }
157         
158         /**
159          * {@inheritDoc}
160          */
161         @Override
162         public void updateToVersion(String newGroupId, String newArtifactId, String newVersion,
163                                             List<TopicCoderFilterConfiguration> decoderConfigurations,
164                                             List<TopicCoderFilterConfiguration> encoderConfigurations) 
165                 throws IllegalArgumentException, LinkageError, Exception {
166                 
167                 if (logger.isInfoEnabled())
168                         logger.info("UPDATE-TO-VERSION: " +  this + " -> {" + newGroupId + ":" + newArtifactId + ":" + newVersion + "}");
169                 
170                 if (newGroupId == null || newArtifactId == null || newVersion == null ||
171                         newGroupId.isEmpty() || newArtifactId.isEmpty() || newVersion.isEmpty()) {
172                                 throw new IllegalArgumentException("Missing maven coordinates: " + 
173                                                                            newGroupId + ":" + newArtifactId + ":" + 
174                                                                            newVersion);
175                 }
176                 
177                 if (newGroupId.equalsIgnoreCase(DroolsController.NO_GROUP_ID) || 
178                         newArtifactId.equalsIgnoreCase(DroolsController.NO_ARTIFACT_ID) || 
179                         newVersion.equalsIgnoreCase(DroolsController.NO_VERSION)) {
180                                 throw new IllegalArgumentException("BRAINLESS maven coordinates provided: " + 
181                                                                            newGroupId + ":" + newArtifactId + ":" + 
182                                                                            newVersion);
183                 }
184                 
185                 if (newGroupId.equalsIgnoreCase(this.getGroupId()) && 
186                         newArtifactId.equalsIgnoreCase(this.getArtifactId()) &&
187                         newVersion.equalsIgnoreCase(this.getVersion())) {
188                                 logger.warn("Al in the right version: " + newGroupId + ":" + 
189                                         newArtifactId + ":" +  newVersion + " vs. " + this);
190                                 return;
191                 }
192                 
193                 if (!newGroupId.equalsIgnoreCase(this.getGroupId()) || 
194                         !newArtifactId.equalsIgnoreCase(this.getArtifactId())) {
195                                 throw new IllegalArgumentException("Group ID and Artifact ID maven coordinates must be identical for the upgrade: " + 
196                                                                                newGroupId + ":" + newArtifactId + ":" + 
197                                                                                newVersion + " vs. " + this);
198                 }
199
200                 /* upgrade */
201                 String messages = this.policyContainer.updateToVersion(newVersion);
202                 if (logger.isWarnEnabled())
203                         logger.warn(this + "UPGRADE results: " + messages);
204                 
205                 /*
206                  * If all sucessful (can load new container), now we can remove all coders from previous sessions
207                  */
208                 this.removeCoders();
209                 
210                 /*
211                  * add the new coders
212                  */
213                 this.init(decoderConfigurations, encoderConfigurations);
214                 
215                 if (logger.isInfoEnabled())
216                         logger.info("UPDATE-TO-VERSION: completed " +  this);
217         }
218
219         /**
220          * initialize decoders for all the topics supported by this controller
221          * Note this is critical to be done after the Policy Container is
222          * instantiated to be able to fetch the corresponding classes.
223          * 
224          * @param decoderConfiguration list of topic -> decoders -> filters mapping
225          */
226         protected void initCoders(List<TopicCoderFilterConfiguration> coderConfigurations, 
227                                           boolean decoder) 
228                   throws IllegalArgumentException {
229                 
230                 if (logger.isInfoEnabled())
231                         logger.info("INIT-CODERS: " +  this);
232                 
233                 if (coderConfigurations == null) {
234                         return;
235                 }
236                         
237
238                 for (TopicCoderFilterConfiguration coderConfig: coderConfigurations) {
239                         String topic = coderConfig.getTopic();
240                         
241                         CustomGsonCoder customGsonCoder = coderConfig.getCustomGsonCoder();
242                         if (coderConfig.getCustomGsonCoder() != null && 
243                                 coderConfig.getCustomGsonCoder().getClassContainer() != null &&
244                                 !coderConfig.getCustomGsonCoder().getClassContainer().isEmpty()) {
245                                         
246                                 String customGsonCoderClass = coderConfig.getCustomGsonCoder().getClassContainer();
247                                 if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(),
248                                                                    customGsonCoderClass)) {
249                                         logger.error(customGsonCoderClass + " cannot be retrieved");
250                                         throw new IllegalArgumentException(customGsonCoderClass + " cannot be retrieved");
251                                 } else {
252                                         if (logger.isInfoEnabled())
253                                                 logger.info("CLASS FETCHED " + customGsonCoderClass);
254                                 }
255                         }
256                         
257                         CustomJacksonCoder customJacksonCoder = coderConfig.getCustomJacksonCoder();
258                         if (coderConfig.getCustomJacksonCoder() != null && 
259                                 coderConfig.getCustomJacksonCoder().getClassContainer() != null &&
260                                 !coderConfig.getCustomJacksonCoder().getClassContainer().isEmpty()) {
261                                 
262                                 String customJacksonCoderClass = coderConfig.getCustomJacksonCoder().getClassContainer();
263                                 if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(),
264                                                                    customJacksonCoderClass)) {
265                                         logger.error(customJacksonCoderClass + " cannot be retrieved");
266                                         throw new IllegalArgumentException(customJacksonCoderClass + " cannot be retrieved");
267                                 } else {
268                                         if (logger.isInfoEnabled())
269                                                 logger.info("CLASS FETCHED " + customJacksonCoderClass);
270                                 }
271                         }       
272                         
273                         List<PotentialCoderFilter> coderFilters = coderConfig.getCoderFilters();
274                         if (coderFilters == null || coderFilters.isEmpty()) {
275                                 continue;
276                         }
277                         
278                         for (PotentialCoderFilter coderFilter : coderFilters) {
279                                 String potentialCodedClass = coderFilter.getCodedClass();
280                                 JsonProtocolFilter protocolFilter = coderFilter.getFilter();
281                                 
282                                 if (!ReflectionUtil.isClass(this.policyContainer.getClassLoader(), 
283                                                                    potentialCodedClass)) {
284                                         logger.error(potentialCodedClass + " cannot be retrieved");
285                                         throw new IllegalArgumentException(potentialCodedClass + " cannot be retrieved");
286                                 } else {
287                                         if (logger.isInfoEnabled())
288                                                 logger.info("CLASS FETCHED " + potentialCodedClass);
289                                 }                       
290                                 
291                                 if (decoder)
292                                         EventProtocolCoder.manager.addDecoder(this.getGroupId(), this.getArtifactId(), 
293                                                                                       topic, potentialCodedClass, protocolFilter,
294                                                                                       customGsonCoder,
295                                                                                       customJacksonCoder,
296                                                                                       this.policyContainer.getClassLoader().hashCode());
297                                 else
298                                         EventProtocolCoder.manager.addEncoder(this.getGroupId(), this.getArtifactId(), 
299                                                           topic, potentialCodedClass, protocolFilter,
300                                                                                       customGsonCoder,
301                                                                                       customJacksonCoder,
302                                                                                       this.policyContainer.getClassLoader().hashCode());
303                         }
304                 }
305         }
306         
307
308         /**
309          * remove decoders.
310          */
311         protected void removeDecoders() 
312                   throws IllegalArgumentException {
313                 if (logger.isInfoEnabled())
314                         logger.info("REMOVE-DECODERS: " +  this);
315                 
316                 if (this.decoderConfigurations == null) {
317                         return;
318                 }
319                         
320
321                 for (TopicCoderFilterConfiguration coderConfig: decoderConfigurations) {
322                         String topic = coderConfig.getTopic();                                                          
323                         EventProtocolCoder.manager.removeDecoders
324                                                 (this.getGroupId(), this.getArtifactId(), topic);
325                 }
326         }
327         
328         /**
329          * remove decoders.
330          */
331         protected void removeEncoders() 
332                   throws IllegalArgumentException {
333                 
334                 if (logger.isInfoEnabled())
335                         logger.info("REMOVE-ENCODERS: " +  this);
336                 
337                 if (this.encoderConfigurations == null)
338                         return;
339                         
340
341                 for (TopicCoderFilterConfiguration coderConfig: encoderConfigurations) {
342                         String topic = coderConfig.getTopic();                                                          
343                         EventProtocolCoder.manager.removeEncoders
344                                                 (this.getGroupId(), this.getArtifactId(), topic);
345                 }
346         }
347         
348
349         /**
350          * {@inheritDoc}
351          */
352         @Override
353         public boolean ownsCoder(Class<? extends Object> coderClass, int modelHash) throws IllegalStateException {
354                 if (!ReflectionUtil.isClass
355                                 (this.policyContainer.getClassLoader(), coderClass.getCanonicalName())) {
356                         logger.error(this + coderClass.getCanonicalName() + " cannot be retrieved. ");
357                         return false;
358                 }
359                 
360                 if (modelHash == this.modelClassLoaderHash) {
361                         if (logger.isInfoEnabled())
362                                 logger.info(coderClass.getCanonicalName() + 
363                                                     this + " class loader matches original drools controller rules classloader " +
364                                         coderClass.getClassLoader());
365                         return true;
366                 } else {
367                         if (logger.isWarnEnabled())
368                                 logger.warn(this + coderClass.getCanonicalName() + " class loaders don't match  " +
369                                 coderClass.getClassLoader() + " vs " + 
370                                                     this.policyContainer.getClassLoader());
371                         return false;
372                 }
373         }
374         
375         /**
376          * {@inheritDoc}
377          */
378         @Override
379         public boolean start() {
380                 
381                 if (logger.isInfoEnabled())
382                         logger.info("START: " + this);
383                 
384                 synchronized (this) {                   
385                         if (this.alive)
386                                 return true;
387                         
388                         this.alive = true;
389                 }
390                 
391                 return this.policyContainer.start();
392         }
393         
394         /**
395          * {@inheritDoc}
396          */
397         @Override
398         public boolean stop() {
399                 
400                 logger.info("STOP: " + this);
401                 
402                 synchronized (this) {
403                         if (!this.alive)
404                                 return true;
405                         
406                         this.alive = false;
407                 }
408                 
409                 return this.policyContainer.stop();
410         }
411
412
413         /**
414          * {@inheritDoc}
415          */
416         @Override
417         public void shutdown() throws IllegalStateException {
418                 
419                 if (logger.isInfoEnabled())
420                         logger.info(this + "SHUTDOWN");
421                 
422                 try {
423                         this.stop();
424                         this.removeCoders();
425                 } catch (Exception e) {
426                         logger.error(MessageCodes.EXCEPTION_ERROR, e, "stop", this.toString());
427                 } finally {
428                         this.policyContainer.shutdown();
429                 }
430
431         }
432         
433
434         /**
435          * {@inheritDoc}
436          */
437         @Override
438         public void halt() throws IllegalStateException {
439                 if (logger.isInfoEnabled())
440                         logger.info(this + "SHUTDOWN");
441                 
442                 try {
443                         this.stop();
444                         this.removeCoders();
445                 } catch (Exception e) {
446                         logger.error(MessageCodes.EXCEPTION_ERROR, e, "halt", this.toString());
447                 } finally {
448                         this.policyContainer.destroy();
449                 }       
450         }
451         
452         /**
453          * removes this drools controllers and encoders and decoders from operation
454          */
455         protected void removeCoders() {
456                 
457                 if (logger.isInfoEnabled())
458                         logger.info(this + "REMOVE-CODERS");
459                 
460                 try {
461                         this.removeDecoders();
462                 } catch (IllegalArgumentException e) {
463                         logger.error(MessageCodes.EXCEPTION_ERROR, e, "removeDecoders", this.toString());
464                 }
465                 
466                 try {
467                         this.removeEncoders();
468                 } catch (IllegalArgumentException e) {
469                         logger.error(MessageCodes.EXCEPTION_ERROR, e, "removeEncoders", this.toString());
470                 }
471         }
472
473         /**
474          * {@inheritDoc}
475          */
476         @Override
477         public boolean isAlive() {
478                 return this.alive;
479         }
480         
481         /**
482          * {@inheritDoc}
483          */
484         @Override
485         public boolean offer(String topic, String event) {
486
487                 if (logger.isInfoEnabled())
488                         logger.info("OFFER: " + topic + ":" + event + " INTO " + this);
489                 
490                 if (this.locked)
491                         return true;
492                 
493                 if (!this.alive)
494                         return true;
495                 
496                 // 0. Check if the policy container has any sessions
497                 
498                 if (this.policyContainer.getPolicySessions().size() <= 0) {
499                         // no sessions
500                         return true;
501                 }
502                 
503                 // 1. Now, check if this topic has a decoder:
504                 
505                 if (!EventProtocolCoder.manager.isDecodingSupported(this.getGroupId(), 
506                                                                               this.getArtifactId(), 
507                                                                               topic)) {
508                         
509                         logger.warn("DECODING-UNSUPPORTED: " + ":" + this.getGroupId() + 
510                                                   ":" + this.getArtifactId() + ":" + topic + " IN " + this);
511                         return true;
512                 }
513                 
514                 // 2. Decode
515                 
516                 Object anEvent;
517                 try {
518                         anEvent = EventProtocolCoder.manager.decode(this.getGroupId(), 
519                                                                               this.getArtifactId(), 
520                                                                               topic, 
521                                                                               event);
522                 } catch (UnsupportedOperationException uoe) {
523                         if (logger.isInfoEnabled())
524                                 logger.info("DECODE:"+ this + ":" + topic + ":" + event);
525                         return true;
526                 } catch (Exception e) {
527                         e.printStackTrace();
528                         logger.error("DECODE:"+ this + ":" + topic + ":" + event);
529                         return true;
530                 }
531                 
532                 synchronized(this.recentSourceEvents) {
533                         this.recentSourceEvents.add(anEvent);
534                 }
535                 
536                 // increment event count for Nagios monitoring
537                 PdpJmx.getInstance().updateOccured();  
538                 
539                 // Broadcast
540                 
541                 if (logger.isInfoEnabled())
542                         logger.info(this + "BROADCAST-INJECT of " + event + " FROM " + topic + " INTO " + this.policyContainer.getName());              
543                 
544                 if (!this.policyContainer.insertAll(anEvent))
545                         logger.warn(this + "Failed to inject into PolicyContainer " + this.getSessionNames());
546                 
547                 return true;
548         }       
549         
550         /**
551          * {@inheritDoc}
552          */
553         @Override
554         public boolean deliver(TopicSink sink, Object event) 
555                         throws IllegalArgumentException, 
556                                IllegalStateException, 
557                            UnsupportedOperationException {
558                 
559                 if (logger.isInfoEnabled())
560                         logger.info(this + "DELIVER: " +  event + " FROM " + this + " TO " + sink);
561                 
562                 if (sink == null)
563                         throw new IllegalArgumentException
564                                                 (this +  " invalid sink");
565                 
566                 if (event == null)
567                         throw new IllegalArgumentException
568                                                 (this +  " invalid event");
569                 
570                 if (this.locked)
571                         throw new IllegalStateException
572                                                         (this +  " is locked");
573                 
574                 if (!this.alive)
575                         throw new IllegalStateException
576                                                         (this +  " is stopped");
577                 
578                 String json =
579                                 EventProtocolCoder.manager.encode(sink.getTopic(), event, this);
580                 
581                 synchronized(this.recentSinkEvents) {
582                         this.recentSinkEvents.add(json);
583                 }
584                 
585                 return sink.send(json);
586                 
587         }
588
589         /**
590          * {@inheritDoc}
591          */
592         @Override
593         public String getVersion() {
594                 return this.policyContainer.getVersion();
595         }
596         
597         /**
598          * {@inheritDoc}
599          */
600         @Override
601         public String getArtifactId() {
602                 return this.policyContainer.getArtifactId();
603         }
604         
605         /**
606          * {@inheritDoc}
607          */
608         @Override
609         public String getGroupId() {
610                 return this.policyContainer.getGroupId();
611         }
612
613         /**
614          * @return the modelClassLoaderHash
615          */
616         public int getModelClassLoaderHash() {
617                 return modelClassLoaderHash;
618         }
619
620         /**
621          * {@inheritDoc}
622          */
623         @Override
624         public synchronized boolean lock() {
625                 logger.info("LOCK: " +  this);
626                 
627                 this.locked = true;
628                 return true;
629         }
630
631         /**
632          * {@inheritDoc}
633          */
634         @Override
635         public synchronized boolean unlock() {
636                 logger.info("UNLOCK: " +  this);
637                 
638                 this.locked = false;
639                 return true;
640         }
641
642         /**
643          * {@inheritDoc}
644          */
645         @Override
646         public boolean isLocked() {
647                 return this.locked;
648         }
649         
650         @Override
651         public List<String> getSessionNames() {
652                 List<String> sessionNames = new ArrayList<String>();
653                 try {
654                         for (PolicySession session: this.policyContainer.getPolicySessions()) {
655                                 sessionNames.add(session.getFullName());
656                         }
657                 } catch (Exception e) {
658                         logger.warn(MessageCodes.EXCEPTION_ERROR, e, 
659                                     "Can't retrieve POLICY-CORE sessions: " + e.getMessage(), 
660                                     this.toString());
661                         sessionNames.add(e.getMessage());
662                 }
663                 return sessionNames;
664         }
665         
666         /**
667          * {@inheritDoc}
668          */
669         @Override
670         public Class<?> fetchModelClass(String className) throws IllegalStateException {
671                 Class<?> modelClass = 
672                         ReflectionUtil.fetchClass(this.policyContainer.getClassLoader(), className);
673                 return modelClass;
674         }
675
676         /**
677          * @return the recentSourceEvents
678          */
679         @Override
680         public Object[] getRecentSourceEvents() {
681                 synchronized(this.recentSourceEvents) {
682                         Object[] events = new Object[recentSourceEvents.size()];
683                         return recentSourceEvents.toArray(events);
684                 }
685         }
686
687         /**
688          * @return the recentSinkEvents
689          */
690         @Override
691         public String[] getRecentSinkEvents() {
692                 synchronized(this.recentSinkEvents) {
693                         String[] events = new String[recentSinkEvents.size()];
694                         return recentSinkEvents.toArray(events);
695                 }
696         }
697         
698
699         /**
700          * {@inheritDoc}
701          */
702         @Override
703         public boolean isBrained() {
704                 return true;
705         }
706         
707
708         @Override
709         public String toString() {
710                 StringBuilder builder = new StringBuilder();
711                 builder.append("MavenDroolsController [policyContainer=")
712                        .append((policyContainer != null) ? policyContainer.getName() : "NULL").append(":")
713                        .append(", alive=")
714                            .append(alive).append(", locked=")
715                            .append(", modelClassLoaderHash=").append(modelClassLoaderHash).append("]");
716                 return builder.toString();
717         }
718
719 }