001    package biz.hammurapi.rules.jsr94.admin;
002    
003    import java.io.File;
004    import java.io.FileInputStream;
005    import java.io.FileOutputStream;
006    import java.io.IOException;
007    import java.io.OutputStream;
008    import java.rmi.RemoteException;
009    import java.util.HashMap;
010    import java.util.Iterator;
011    import java.util.Map;
012    
013    import javax.rules.RuleServiceProvider;
014    import javax.rules.RuleServiceProviderManager;
015    import javax.rules.admin.LocalRuleExecutionSetProvider;
016    import javax.rules.admin.Rule;
017    import javax.rules.admin.RuleAdministrator;
018    import javax.rules.admin.RuleExecutionSet;
019    import javax.rules.admin.RuleExecutionSetDeregistrationException;
020    import javax.rules.admin.RuleExecutionSetProvider;
021    import javax.rules.admin.RuleExecutionSetRegisterException;
022    import javax.xml.parsers.DocumentBuilderFactory;
023    import javax.xml.parsers.FactoryConfigurationError;
024    import javax.xml.parsers.ParserConfigurationException;
025    import javax.xml.transform.TransformerException;
026    import javax.xml.transform.stream.StreamResult;
027    import javax.xml.xpath.XPathExpressionException;
028    
029    import org.apache.commons.cli.CommandLine;
030    import org.apache.commons.cli.CommandLineParser;
031    import org.apache.commons.cli.HelpFormatter;
032    import org.apache.commons.cli.Option;
033    import org.apache.commons.cli.OptionBuilder;
034    import org.apache.commons.cli.Options;
035    import org.apache.commons.cli.PosixParser;
036    import org.w3c.dom.Document;
037    import org.w3c.dom.Element;
038    import org.w3c.dom.NodeList;
039    import org.xml.sax.SAXException;
040    
041    import biz.hammurapi.config.ConfigurationException;
042    import biz.hammurapi.rules.jsr94.FileRuleServiceProvider;
043    import biz.hammurapi.rules.jsr94.FileRuleServiceProvider.Registration;
044    import biz.hammurapi.util.StreamPumper;
045    import biz.hammurapi.xml.dom.AbstractDomObject;
046    import biz.hammurapi.xml.dom.DOMUtils;
047    import biz.hammurapi.xml.dom.DomSerializable;
048    
049    
050    /**
051     * This rule administrator stores rulesets in an XML file.
052     * @author Pavel Vlasov
053     * @revision $Revision$
054     */
055    public class FileRuleAdministrator implements javax.rules.admin.RuleAdministrator {
056            
057            private Map registrations;
058            private File regFile;
059    
060            /**
061             * Creates new instance.
062             * @param registrations
063             * @param homeDir
064             */
065            public FileRuleAdministrator(Map registrations, File regFile) {
066                    this.registrations=registrations;
067                    this.regFile=regFile;
068            }
069            
070            /**
071             * Loads registrations from a file.
072             * @throws SAXException
073             * @throws IOException
074             * @throws ParserConfigurationException
075             * @throws FactoryConfigurationError
076             * @throws TransformerException
077             * @throws ConfigurationException
078             * @throws XPathExpressionException 
079             */
080            private void load() throws SAXException, IOException, ParserConfigurationException, FactoryConfigurationError, ConfigurationException, XPathExpressionException {
081                    if (regFile.exists()) {
082                            registrations.clear();
083                            Document doc=DOMUtils.parse(regFile);
084                            NodeList nl=DOMUtils.selectNodeList(doc.getDocumentElement(), "/registrations/registration");
085                            for (int i=0, l=nl.getLength(); i<l; ++i) {
086                                    Registration r=new FileRuleServiceProvider.Registration(nl.item(i));
087                                    registrations.put(r.getUri(), r);
088                            }
089                    }
090            }       
091            
092            /**
093             * Stores registrations to a file.
094             * @throws ParserConfigurationException
095             * @throws FactoryConfigurationError
096             * @throws TransformerException 
097             * @throws IOException 
098             */
099            private void store() throws ParserConfigurationException, FactoryConfigurationError, IOException, TransformerException {
100                    DOMUtils.serialize(registrations2Dom().getDocumentElement(), regFile);
101            }
102    
103            /**
104             * @return
105             * @throws ParserConfigurationException
106             * @throws FactoryConfigurationError
107             */
108            private Document registrations2Dom() throws ParserConfigurationException, FactoryConfigurationError {
109                    Document doc=DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
110                    Element root = AbstractDomObject.addElement(doc, "registrations");
111                    Iterator rit=registrations.values().iterator();
112                    while (rit.hasNext()) {
113                            ((DomSerializable) rit.next()).toDom(AbstractDomObject.addElement(root, "registration"));
114                    }
115                    return doc;
116            }       
117    
118            /**
119             * Creates new RuleExecutionSetProvider. Propeties are passed to the provider.
120             */
121            public RuleExecutionSetProvider getRuleExecutionSetProvider(Map properties) throws RemoteException {
122                    return new biz.hammurapi.rules.jsr94.admin.RuleExecutionSetProvider(properties);
123            }
124    
125            /**
126             * Creates new LocalRuleExecutionSetProvider. Properties are passed to the provider.
127             */
128            public LocalRuleExecutionSetProvider getLocalRuleExecutionSetProvider(Map properties) throws RemoteException {
129                    return new biz.hammurapi.rules.jsr94.admin.LocalRuleExecutionSetProvider(properties);
130            }
131    
132            /**
133             * Registers rule execution set.
134             * Rule execution set can be registered by reference and by value.
135             * When registered by reference the set url is stored in the file instead of actual XML definition.
136             * To store rule execution set by reference put {"by-reference", Boolean.TRUE} to the properties map.
137             */
138            public void registerRuleExecutionSet(String uri, RuleExecutionSet res, Map properties) throws RuleExecutionSetRegisterException, RemoteException {
139                    if (uri.startsWith("direct:")) {
140                            throw new RuleExecutionSetRegisterException("Invalid rule set URI, 'direct:' is a reserved prefix.");
141                    }
142                    boolean byRef=properties!=null && Boolean.TRUE.equals(properties.get("by-reference"));
143                    biz.hammurapi.rules.jsr94.admin.RuleExecutionSet cRes = ((biz.hammurapi.rules.jsr94.admin.RuleExecutionSet) res);
144                    if (byRef && cRes.isModified()) {
145                            throw new RuleExecutionSetRegisterException("Cannot register modified rule set by reference");
146                    }
147                    
148                    if (byRef && cRes.getUri()==null) {
149                            throw new RuleExecutionSetRegisterException("Cannot register rule set by reference - URI is null");
150                    }
151                    
152                    synchronized (registrations) {
153                            try {
154                                    load();
155                                    if (byRef) {
156                                            registrations.put(uri, new FileRuleServiceProvider.Registration(uri, cRes.getUri(), null, cRes.getProperties()));
157                                    } else {
158                                            registrations.put(uri, new FileRuleServiceProvider.Registration(uri, null, cRes.getHolder(), cRes.getProperties()));                                    
159                                    }
160                                    store();
161                            } catch (Exception e) {
162                                    throw new RuleExecutionSetRegisterException("Could not deregister rule set with URI: "+uri+", "+e, e);
163                            }
164                    }
165            }
166    
167            /**
168             * Properties are ignored.
169             */
170            public void deregisterRuleExecutionSet(String uri, Map properties) throws RuleExecutionSetDeregistrationException, RemoteException {
171                    synchronized (registrations) {
172                            try {
173                                    load();
174                                    registrations.remove(uri);
175                                    store();
176                            } catch (Exception e) {
177                                    throw new RuleExecutionSetDeregistrationException("Could not deregister rule set with URI: "+uri+", "+e, e);
178                            }                       
179                    }
180            }
181            
182            public static void main(String[] args) throws Exception {
183                System.out.println("Hammurapi Rules administrator. Copyright (C) 2006 Hammurapi Group");
184                    String ruleServiceProviderClassName = "biz.hammurapi.rules.jsr94.FileRuleServiceProvider";
185                    Class.forName(ruleServiceProviderClassName);
186                    RuleServiceProvider serviceProvider = RuleServiceProviderManager.getRuleServiceProvider(ruleServiceProviderClassName);
187                    RuleAdministrator administrator = serviceProvider.getRuleAdministrator();
188                                
189                if (args.length==0) {
190                    printHelpAndExit();
191                } else if ("register".equals(args[0])) {
192                        Options options=new Options();
193                                        
194                        Option helpOption=OptionBuilder
195                        .withDescription("Print this message")
196                        .isRequired(false)
197                        .create("h");
198                        
199                        options.addOption(helpOption);
200    
201                        Option fileOption=OptionBuilder
202                        .withArgName("file")
203                        .hasArg()
204                        .withDescription("Ruleset file")
205                        .isRequired(false)
206                        .create("f");
207                        
208                        options.addOption(fileOption);
209                        
210                        
211                        Option urlOption=OptionBuilder
212                        .withArgName("URL")
213                        .hasArg()
214                        .withDescription("Ruleset URL")
215                        .isRequired(false)
216                        .create("u");
217                        
218                        options.addOption(urlOption);
219                        
220                        Option refOption=OptionBuilder
221                        .withArgName("URL")
222                        .hasArg()
223                        .withDescription("Ruleset URL to be registered by reference")
224                        .isRequired(false)
225                        .create("r");
226                        
227                        options.addOption(refOption);
228                        
229                        Option propertyOption=OptionBuilder
230                        .withArgName("key=value")
231                        .hasArg()
232                        .withDescription("Rule set property")
233                        .isRequired(false)
234                        .create("p");
235                        
236                        options.addOption(propertyOption);
237                        
238                        Option rulePropertyOption=OptionBuilder
239                        .withArgName("rule name>:<key>=<value")
240                        .hasArg()
241                        .withDescription("Rule property")
242                        .isRequired(false)
243                        .create("R");
244                        
245                        options.addOption(rulePropertyOption);
246                        
247                        Option objectFilterOption=OptionBuilder
248                        .withArgName("class name")
249                        .hasArg()
250                        .withDescription("Default object filter")
251                        .isRequired(false)
252                        .create("o");
253                        
254                        options.addOption(objectFilterOption);
255                        
256                        CommandLineParser parser=new PosixParser();
257                        CommandLine line=null;
258                        
259                        try {
260                            line=parser.parse(options, args);
261                        } catch (org.apache.commons.cli.ParseException e) {
262                            System.err.println(e.getMessage());
263                            System.err.flush();
264                            printHelpAndExit("register <uri>", options);
265                            }
266                        
267                        if (line.hasOption("h")) {
268                            printHelpAndExit("register <uri>", options);
269                        }
270                    
271                        int no=0;
272                        if (line.hasOption('f')) {
273                            ++no;
274                        }
275                        if (line.hasOption('u')) {
276                            ++no;
277                        }
278                        if (line.hasOption('r')) {
279                            ++no;
280                        }
281                        
282                        if (no==0) {
283                            System.out.println("One of -f, -u or -r options must be specified");
284                            printHelpAndExit("register <uri>", options);
285                        }
286                        
287                        if (no>1) {
288                            System.out.println("Options -f, -u, and -r are mutually exclusive");
289                            printHelpAndExit("register <uri>", options);
290                        }
291                        
292                            String[] largs=line.getArgs();
293                        if (largs.length!=2) {
294                            System.out.println("Invalid parameters");
295                            printHelpAndExit("register <uri>", options);
296                        }
297                        
298                            RuleExecutionSetProvider resp = administrator.getRuleExecutionSetProvider(null);
299                            
300                            Map properties=null;
301                            if (line.hasOption('p')) {
302                                    properties=new HashMap();
303                                    String[] props=line.getOptionValues('p');
304                                    for (int i=0; i<props.length; i++) {
305                                            int idx=props[i].indexOf('=');
306                                            if (idx==-1 || idx==props[i].length()-1 || idx==0) {
307                                                    System.out.println("Invalid property definition: "+props[i]);
308                                            printHelpAndExit("register <uri>", options);                                              
309                                            }
310                                            properties.put(props[i].substring(0, idx), props[i].substring(idx+1));                                  
311                                    }
312                            }                       
313                        
314                        if (line.hasOption('f')) {
315                                    RuleExecutionSet res = resp.createRuleExecutionSet(DOMUtils.parse(new File(line.getOptionValue('f'))).getDocumentElement(), properties);
316                                    setObjectFilterAndRuleProperties(line, options, res);
317                                    administrator.registerRuleExecutionSet(largs[1], res, null);
318                        }
319                        
320                        if (line.hasOption('u')) {
321                                    RuleExecutionSet res = resp.createRuleExecutionSet(line.getOptionValue('u'), properties);
322                                    setObjectFilterAndRuleProperties(line, options, res);
323                                    administrator.registerRuleExecutionSet(largs[1], res, null);
324                        }
325                        
326                        if (line.hasOption('r')) {
327                            if (line.hasOption('R')) {
328                                    System.out.println("Rule properties cannot be set in rule sets registered by reference");
329                                    printHelpAndExit("register <uri>", options);                              
330                            }
331                            
332                            if (line.hasOption('o')) {
333                                    System.out.println("Defalut object filter cannot be set in rule sets registered by reference");
334                                    printHelpAndExit("register <uri>", options);                              
335                            }
336                            
337                                    RuleExecutionSet res = resp.createRuleExecutionSet(line.getOptionValue('r'), properties);
338                                    HashMap regProperties = new HashMap();
339                                    regProperties.put("by-reference", Boolean.TRUE);
340                                    administrator.registerRuleExecutionSet(largs[1], res, regProperties);
341                        }               
342                        
343                } else if ("deregister".equals(args[0])) {
344                        Options options=new Options();
345                        
346                        Option helpOption=OptionBuilder
347                        .withDescription("Print this message")
348                        .isRequired(false)
349                        .create("h");
350                        
351                        options.addOption(helpOption);
352                        
353                        CommandLineParser parser=new PosixParser();
354                        CommandLine line=null;
355                        
356                        try {
357                            line=parser.parse(options, args);
358                        } catch (org.apache.commons.cli.ParseException e) {
359                            System.err.println(e.getMessage());
360                            System.err.flush();
361                            printHelpAndExit("deregister <uri>", options);
362                            }
363                        
364                        if (line.hasOption("h")) {
365                            printHelpAndExit("deregister <uri>", options);
366                        }
367                    
368                            String[] largs=line.getArgs();
369                        if (largs.length!=2) {
370                            System.out.println("Invalid parameters");
371                            printHelpAndExit("deregister <uri>", options);
372                        }
373                        
374                            administrator.deregisterRuleExecutionSet(largs[1], null);
375                } else if ("list".equals(args[0])) {
376                        Options options=new Options();
377                        
378                        Option helpOption=OptionBuilder
379                        .withDescription("Print this message")
380                        .isRequired(false)
381                        .create("h");
382                        
383                        options.addOption(helpOption);
384    
385                        Option fileOption=OptionBuilder
386                        .withArgName("file")
387                        .hasArg()
388                        .withDescription("Output file")
389                        .isRequired(false)
390                        .create("f");
391                        
392                        options.addOption(fileOption);                  
393                        
394                        Option xmlOption=OptionBuilder
395                        .withDescription("Output in XML format")
396                        .isRequired(false)
397                        .create("x");
398                        
399                        options.addOption(xmlOption);
400                        
401                        
402                        Option htmlOption=OptionBuilder
403                        .withDescription("Output in HTML format")
404                        .isRequired(false)
405                        .create("H");
406                        
407                        options.addOption(htmlOption);
408                        
409                        CommandLineParser parser=new PosixParser();
410                        CommandLine line=null;
411                        
412                        try {
413                            line=parser.parse(options, args);
414                        } catch (org.apache.commons.cli.ParseException e) {
415                            System.err.println(e.getMessage());
416                            System.err.flush();
417                            printHelpAndExit("list", options);
418                            }
419                        
420                        if (line.hasOption("h")) {
421                            printHelpAndExit("list", options);
422                        }
423                    
424                        OutputStream out;
425                        if (line.hasOption('f')) {
426                            out=new FileOutputStream(new File(line.getOptionValue('f')));                   
427                        } else {
428                            out=System.out;
429                        }
430                        
431                        File regFile=((FileRuleAdministrator) administrator).regFile;
432                        
433                        if (line.hasOption('x')) {
434                            FileInputStream in=new FileInputStream(regFile);
435                            try {
436                                    new StreamPumper(in, out, null, false).run();
437                            } finally {
438                                    in.close();
439                                    if (line.hasOption('f')) {
440                                            out.close();
441                                    }
442                            }
443                        } else if (line.hasOption('H')) {
444                            try {
445                                    DOMUtils.style(
446                                                    DOMUtils.parse(regFile), 
447                                                    out,
448                                                    FileRuleAdministrator.class.getResourceAsStream("list_html.xsl"),
449                                                    null);
450                            } finally {
451                                    if (line.hasOption('f')) {
452                                            out.close();
453                                    }
454                            }
455                        } else {
456                            try {
457                                    DOMUtils.style(
458                                                    DOMUtils.parse(regFile), 
459                                                    out,
460                                                    FileRuleAdministrator.class.getResourceAsStream("list_text.xsl"),
461                                                    null);
462                            } finally {
463                                    if (line.hasOption('f')) {
464                                            out.close();
465                                    }
466                            }                       
467                        }
468                } else if ("dump".equals(args[0])) {
469                        Options options=new Options();
470                        
471                        Option helpOption=OptionBuilder
472                        .withDescription("Print this message")
473                        .isRequired(false)
474                        .create("h");
475                        
476                        options.addOption(helpOption);
477    
478                        Option fileOption=OptionBuilder
479                        .withArgName("file")
480                        .hasArg()
481                        .withDescription("Output file")
482                        .isRequired(false)
483                        .create("f");
484                        
485                        options.addOption(fileOption);                  
486                        
487                        Option xmlOption=OptionBuilder
488                        .withDescription("Output in XML format")
489                        .isRequired(false)
490                        .create("x");
491                        
492                        options.addOption(xmlOption);
493                        
494                        
495                        Option htmlOption=OptionBuilder
496                        .withDescription("Output in HTML format")
497                        .isRequired(false)
498                        .create("H");
499                        
500                        options.addOption(htmlOption);
501                        
502                        CommandLineParser parser=new PosixParser();
503                        CommandLine line=null;
504                        
505                        try {
506                            line=parser.parse(options, args);
507                        } catch (org.apache.commons.cli.ParseException e) {
508                            System.err.println(e.getMessage());
509                            System.err.flush();
510                            printHelpAndExit("dump <uri>", options);
511                            }
512                        
513                        if (line.hasOption("h")) {
514                            printHelpAndExit("dump <uri>", options);
515                        }
516                    
517                            String[] largs=line.getArgs();
518                        if (largs.length!=2) {
519                            System.out.println("Invalid parameters");
520                            printHelpAndExit("dump <uri>", options);
521                        }
522                        
523                        OutputStream out;
524                        if (line.hasOption('f')) {
525                            out=new FileOutputStream(new File(line.getOptionValue('f')));                   
526                        } else {
527                            out=System.out;
528                        }
529                        
530                        Object registration = ((FileRuleAdministrator) administrator).registrations.get(largs[1]);
531                        
532                        if (line.hasOption('x')) {
533                            try {
534                                    DOMUtils.serialize(registration, "registration", new StreamResult(out));
535                            } finally {
536                                    if (line.hasOption('f')) {
537                                            out.close();
538                                    }
539                            }
540                        } else if (line.hasOption('H')) {
541                            try {
542                                    DOMUtils.style(
543                                                    registration,
544                                                    "registration",
545                                                    out,
546                                                    FileRuleAdministrator.class.getResourceAsStream("dump_html.xsl"),
547                                                    null);
548                            } finally {
549                                    if (line.hasOption('f')) {
550                                            out.close();
551                                    }
552                            }
553                        } else {
554                            try {
555                                    DOMUtils.style(
556                                                    registration,
557                                                    "registration",
558                                                    out,
559                                                    FileRuleAdministrator.class.getResourceAsStream("dump_text.xsl"),
560                                                    null);
561                            } finally {
562                                    if (line.hasOption('f')) {
563                                            out.close();
564                                    }
565                            }                       
566                        }
567                } else {
568                    System.out.println("Unrecognized command: "+args[0]);
569                    printHelpAndExit();
570                }           
571        }
572            
573            /**
574             * Reads default object filter and rule properties from command line and sets them in RuleExecutionSet.
575             * @param line
576             * @param res
577             */
578            private static void setObjectFilterAndRuleProperties(CommandLine line, Options options, RuleExecutionSet res) {
579                    if (line.hasOption('o')) {
580                            res.setDefaultObjectFilter(line.getOptionValue('o'));
581                    }
582                    
583                    if (line.hasOption('R')) {
584                            String[] ruleProperties=line.getOptionValues('R');
585                            
586                            Z: for (int i=0; i<ruleProperties.length; i++) {
587                                    int idx=ruleProperties[i].indexOf(":");
588                                    if (idx<=0 || idx==ruleProperties[i].length()-1) {
589                                            System.out.println("Rule property format: <Rule name>:<property name>=<property value>");
590                                    printHelpAndExit("register <uri>", options);                                              
591                                    }
592                                    
593                                    String ruleName=ruleProperties[i].substring(0, idx);
594                                    
595                                    int idx2=ruleProperties[i].indexOf("=", idx);
596                                    if (idx2==-1 || idx2==ruleProperties[i].length()-1) {
597                                            System.out.println("Rule property format: <Rule name>:<property name>=<property value>");
598                                    printHelpAndExit("register <uri>", options);                                              
599                                    }
600                                    
601                                    String propertyName=ruleProperties[i].substring(idx+1, idx2);
602                                    String propertyValue=ruleProperties[i].substring(idx2+1);
603                                    
604                                    Iterator it=res.getRules().iterator();
605                                    while (it.hasNext()) {
606                                            Rule rule=(Rule) it.next();
607                                            if (ruleName.equals(rule.getName())) {
608                                                    rule.setProperty(propertyName, propertyValue);
609                                                    continue Z;
610                                            }
611                                    }
612                                    
613                                    System.out.println("Rule not found: "+ruleName);
614                            printHelpAndExit("register <uri>", options);                                                                              
615                            }
616                    }
617                    
618            }
619    
620            protected static void printHelpAndExit() {
621                System.out.println("Usage: hradmin command [options]");
622                System.out.println("\tCommands:");
623                System.out.println("\t\tregister <uri> {-f <file> | -u <url> | -r <url>} [-p <name>=<value>] [-R <rule name>:<property name>=<property value>] [-o <object filter>]- Registers rule set.");
624                System.out.println("\t\tderegister <uri> - Unregisters rule set.");
625                System.out.println("\t\tlist [-f <file>] {-x|-H} - Lists names of registered rule sets to console or file.");
626                System.out.println("\t\tdump <uri>  [-f <file>] {-x|-H} - Outputs ruleset definition to console or file.");
627                System.out.println("\n\tExecute 'hradmin command -h' for command-specific options");            
628                
629                System.exit(1);
630            }
631            
632            protected static void printHelpAndExit(String command, Options options) {
633                HelpFormatter formatter=new HelpFormatter();
634                formatter.printHelp("hradmin "+command+" [options]", options, false);
635                System.exit(1);
636            }
637            
638    
639    }