001// Copyright 2004, 2005 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007//     http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.hivemind.parse;
016
017import java.io.BufferedInputStream;
018import java.io.IOException;
019import java.io.InputStream;
020import java.util.Enumeration;
021import java.util.HashMap;
022import java.util.Iterator;
023import java.util.Map;
024import java.util.Properties;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.apache.hivemind.ApplicationRuntimeException;
029import org.apache.hivemind.Attribute;
030import org.apache.hivemind.ClassResolver;
031import org.apache.hivemind.ErrorHandler;
032import org.apache.hivemind.Occurances;
033import org.apache.hivemind.Resource;
034import org.apache.hivemind.impl.AttributeImpl;
035import org.apache.hivemind.impl.ElementImpl;
036import org.apache.hivemind.internal.Visibility;
037import org.apache.hivemind.schema.ElementModel;
038import org.apache.hivemind.schema.Rule;
039import org.apache.hivemind.schema.impl.AttributeModelImpl;
040import org.apache.hivemind.schema.impl.ElementModelImpl;
041import org.apache.hivemind.schema.impl.SchemaImpl;
042import org.apache.hivemind.schema.rules.CreateObjectRule;
043import org.apache.hivemind.schema.rules.InvokeParentRule;
044import org.apache.hivemind.schema.rules.PushAttributeRule;
045import org.apache.hivemind.schema.rules.PushContentRule;
046import org.apache.hivemind.schema.rules.ReadAttributeRule;
047import org.apache.hivemind.schema.rules.ReadContentRule;
048import org.apache.hivemind.schema.rules.SetModuleRule;
049import org.apache.hivemind.schema.rules.SetParentRule;
050import org.apache.hivemind.schema.rules.SetPropertyRule;
051import org.apache.hivemind.util.IdUtils;
052import org.apache.oro.text.regex.MalformedPatternException;
053import org.apache.oro.text.regex.Pattern;
054import org.apache.oro.text.regex.Perl5Compiler;
055import org.apache.oro.text.regex.Perl5Matcher;
056
057/**
058 * Used to parse HiveMind module deployment descriptors.
059 * <p>
060 * TODO: The parser ignores element content except inside &lt;contribution&gt; and
061 * &lt;invoke-factory&gt; ... it probably should forbid non-whitespace content.
062 * 
063 * @author Howard Lewis Ship
064 */
065public final class DescriptorParser extends AbstractParser
066{
067    private static final String DEFAULT_SERVICE_MODEL = "singleton";
068
069    private static final Log LOG = LogFactory.getLog(DescriptorParser.class);
070
071    /**
072     * States used while parsing the document. Most states correspond to a particular XML element in
073     * the document. STATE_START is the initial state, before the &lt;module&gt; element is reached.
074     */
075    private static final int STATE_START = 0;
076
077    private static final int STATE_MODULE = 1;
078
079    // private static final int STATE_DESCRIPTION = 2;
080    private static final int STATE_CONFIGURATION_POINT = 3;
081
082    private static final int STATE_CONTRIBUTION = 4;
083
084    private static final int STATE_SERVICE_POINT = 5;
085
086    private static final int STATE_CREATE_INSTANCE = 6;
087
088    private static final int STATE_IMPLEMENTATION = 8;
089
090    /**
091     * Used for both &lt;schema&;gt; within a &lt;extension-point&gt;, and for
092     * &lt;parameters-schema&gt; within a &lt;service&gt;.
093     */
094    private static final int STATE_SCHEMA = 9;
095
096    private static final int STATE_ELEMENT = 10;
097
098    private static final int STATE_RULES = 11;
099
100    /**
101     * Used with &lt;invoke-factory&gt; and &lt;interceptor&gt; to collect parameters that will be
102     * passed to the implementation or interceptor factory service.
103     */
104    private static final int STATE_COLLECT_SERVICE_PARAMETERS = 12;
105
106    /**
107     * Used with the &lt;conversion&gt; element (an alternative to using &lt;rules&gt;. Finds
108     * &lt;map&gt; elements.
109     */
110    private static final int STATE_CONVERSION = 13;
111
112    /**
113     * Represents building Element hierarchy as a light-wieght DOM.
114     */
115
116    private static final int STATE_LWDOM = 100;
117
118    /**
119     * Special state for elements that are not allowed to contain any other elements.
120     */
121
122    private static final int STATE_NO_CONTENT = 300;
123
124    private static final String SIMPLE_ID = "[a-zA-Z0-9_]+";
125
126    /**
127     * Format for configuration point ids, service point ids and schema ids. Consists of an optional
128     * leading underscore, followed by alphanumerics and underscores. Normal naming convention is to
129     * use a single CamelCase word, like a Java class name.
130     */
131    public static final String ID_PATTERN = "^" + SIMPLE_ID + "$";
132
133    /**
134     * Module ids are a sequence of simple ids seperated by periods. In practice, they look like
135     * Java package names.
136     */
137    public static final String MODULE_ID_PATTERN = "^" + SIMPLE_ID + "(\\." + SIMPLE_ID + ")*$";
138
139    public static final String VERSION_PATTERN = "[0-9]+(\\.[0-9]+){2}$";
140
141    /**
142     * Temporary storage of the current {@link org.xml.sax.Attributes}.
143     */
144    private Map _attributes = new HashMap();
145
146    /**
147     * Built from DescriptorParser.properties. Key is element name, value is an instance of
148     * {@link ElementParseInfo}.
149     */
150
151    private Map _elementParseInfo = new HashMap();
152
153    private ModuleDescriptor _moduleDescriptor;
154
155    private ErrorHandler _errorHandler;
156
157    private ClassResolver _resolver;
158
159    private Perl5Compiler _compiler;
160
161    private Perl5Matcher _matcher;
162
163    private Map _compiledPatterns;
164
165    /**
166     * Map of Rule keyed on class name, used with &lt;custom&gt; rules.
167     */
168    private final Map _ruleMap = new HashMap();
169
170    private final Map OCCURS_MAP = new HashMap();
171
172    {
173        OCCURS_MAP.put("0..1", Occurances.OPTIONAL);
174        OCCURS_MAP.put("1", Occurances.REQUIRED);
175        OCCURS_MAP.put("1..n", Occurances.ONE_PLUS);
176        OCCURS_MAP.put("0..n", Occurances.UNBOUNDED);
177        OCCURS_MAP.put("none", Occurances.NONE);
178    }
179
180    private final Map VISIBILITY_MAP = new HashMap();
181
182    {
183        VISIBILITY_MAP.put("public", Visibility.PUBLIC);
184        VISIBILITY_MAP.put("private", Visibility.PRIVATE);
185    }
186
187    public DescriptorParser(ErrorHandler errorHandler)
188    {
189        _errorHandler = errorHandler;
190
191        initializeFromPropertiesFile();
192    }
193
194    public void begin(String elementName, Map attributes)
195    {
196        _attributes = attributes;
197
198        switch (getState())
199        {
200            case STATE_START:
201
202                beginStart(elementName);
203                break;
204
205            case STATE_MODULE:
206
207                beginModule(elementName);
208                break;
209
210            case STATE_CONFIGURATION_POINT:
211
212                beginConfigurationPoint(elementName);
213                break;
214
215            case STATE_CONTRIBUTION:
216
217                beginContribution(elementName);
218                break;
219
220            case STATE_LWDOM:
221
222                beginLWDom(elementName);
223                break;
224
225            case STATE_SERVICE_POINT:
226
227                beginServicePoint(elementName);
228                break;
229
230            case STATE_IMPLEMENTATION:
231
232                beginImplementation(elementName);
233                break;
234
235            case STATE_SCHEMA:
236
237                beginSchema(elementName);
238                break;
239
240            case STATE_ELEMENT:
241
242                beginElement(elementName);
243                break;
244
245            case STATE_RULES:
246
247                beginRules(elementName);
248                break;
249
250            case STATE_COLLECT_SERVICE_PARAMETERS:
251
252                beginCollectServiceParameters(elementName);
253                break;
254
255            case STATE_CONVERSION:
256
257                beginConversion(elementName);
258                break;
259
260            default:
261
262                unexpectedElement(elementName);
263                break;
264        }
265    }
266
267    /**
268     * Very similar to {@link #beginContribution(String)}, in that it creates an
269     * {@link ElementImpl}, adds it as a parameter to the
270     * {@link AbstractServiceInvocationDescriptor}, then enters STATE_LWDOM to fill in its
271     * attributes and content.
272     */
273
274    private void beginCollectServiceParameters(String elementName)
275    {
276        ElementImpl element = buildLWDomElement(elementName);
277
278        AbstractServiceInvocationDescriptor sid = (AbstractServiceInvocationDescriptor) peekObject();
279
280        sid.addParameter(element);
281
282        push(elementName, element, STATE_LWDOM, false);
283    }
284
285    /**
286     * Invoked when a new element starts within STATE_CONFIGURATION_POINT.
287     */
288    private void beginConfigurationPoint(String elementName)
289    {
290        if (elementName.equals("schema"))
291        {
292            enterEmbeddedConfigurationPointSchema(elementName);
293            return;
294        }
295
296        unexpectedElement(elementName);
297    }
298
299    private void beginContribution(String elementName)
300    {
301        // This is where things get tricky, the point where we outgrew Jakarta Digester.
302
303        ElementImpl element = buildLWDomElement(elementName);
304
305        ContributionDescriptor ed = (ContributionDescriptor) peekObject();
306        ed.addElement(element);
307
308        push(elementName, element, STATE_LWDOM, false);
309    }
310
311    private void beginConversion(String elementName)
312    {
313        if (elementName.equals("map"))
314        {
315            ConversionDescriptor cd = (ConversionDescriptor) peekObject();
316
317            AttributeMappingDescriptor amd = new AttributeMappingDescriptor();
318
319            push(elementName, amd, STATE_NO_CONTENT);
320
321            checkAttributes();
322
323            amd.setAttributeName(getAttribute("attribute"));
324            amd.setPropertyName(getAttribute("property"));
325
326            cd.addAttributeMapping(amd);
327
328            return;
329        }
330
331        unexpectedElement(elementName);
332    }
333
334    private void beginElement(String elementName)
335    {
336        if (elementName.equals("attribute"))
337        {
338            enterAttribute(elementName);
339            return;
340        }
341
342        if (elementName.equals("conversion"))
343        {
344            enterConversion(elementName);
345            return;
346        }
347
348        if (elementName.equals("rules"))
349        {
350            enterRules(elementName);
351            return;
352        }
353
354        // <element> is recursive ... possible, but tricky, if using Digester.
355
356        if (elementName.equals("element"))
357        {
358            ElementModelImpl elementModel = (ElementModelImpl) peekObject();
359
360            elementModel.addElementModel(enterElement(elementName));
361            return;
362        }
363
364        unexpectedElement(elementName);
365    }
366
367    private void beginImplementation(String elementName)
368    {
369
370        if (elementName.equals("create-instance"))
371        {
372            enterCreateInstance(elementName);
373            return;
374        }
375
376        if (elementName.equals("invoke-factory"))
377        {
378            enterInvokeFactory(elementName);
379            return;
380        }
381
382        if (elementName.equals("interceptor"))
383        {
384            enterInterceptor(elementName);
385            return;
386        }
387
388        unexpectedElement(elementName);
389    }
390
391    private void beginLWDom(String elementName)
392    {
393        ElementImpl element = buildLWDomElement(elementName);
394
395        ElementImpl parent = (ElementImpl) peekObject();
396        parent.addElement(element);
397
398        push(elementName, element, STATE_LWDOM, false);
399    }
400
401    /**
402     * Invoked when a new element occurs while in STATE_MODULE.
403     */
404    private void beginModule(String elementName)
405    {
406        if (elementName.equals("configuration-point"))
407        {
408            enterConfigurationPoint(elementName);
409
410            return;
411        }
412
413        if (elementName.equals("contribution"))
414        {
415            enterContribution(elementName);
416            return;
417        }
418
419        if (elementName.equals("service-point"))
420        {
421            enterServicePoint(elementName);
422
423            return;
424        }
425
426        if (elementName.equals("implementation"))
427        {
428            enterImplementation(elementName);
429
430            return;
431        }
432
433        if (elementName.equals("schema"))
434        {
435            enterSchema(elementName);
436            return;
437        }
438
439        if (elementName.equals("sub-module"))
440        {
441            enterSubModule(elementName);
442
443            return;
444        }
445
446        if (elementName.equals("dependency"))
447        {
448            enterDependency(elementName);
449
450            return;
451        }
452
453        unexpectedElement(elementName);
454    }
455
456    private void beginRules(String elementName)
457    {
458
459        if (elementName.equals("create-object"))
460        {
461            enterCreateObject(elementName);
462            return;
463        }
464
465        if (elementName.equals("invoke-parent"))
466        {
467            enterInvokeParent(elementName);
468            return;
469        }
470
471        if (elementName.equals("read-attribute"))
472        {
473            enterReadAttribute(elementName);
474            return;
475        }
476
477        if (elementName.equals("read-content"))
478        {
479            enterReadContent(elementName);
480            return;
481        }
482
483        if (elementName.equals("set-module"))
484        {
485            enterSetModule(elementName);
486            return;
487        }
488
489        if (elementName.equals("set-property"))
490        {
491            enterSetProperty(elementName);
492            return;
493        }
494
495        if (elementName.equals("push-attribute"))
496        {
497            enterPushAttribute(elementName);
498            return;
499        }
500
501        if (elementName.equals("push-content"))
502        {
503            enterPushContent(elementName);
504            return;
505        }
506
507        if (elementName.equals("set-parent"))
508        {
509            enterSetParent(elementName);
510            return;
511        }
512
513        if (elementName.equals("custom"))
514        {
515            enterCustom(elementName);
516
517            return;
518        }
519
520        unexpectedElement(elementName);
521    }
522
523    private void beginSchema(String elementName)
524    {
525        if (elementName.equals("element"))
526        {
527            SchemaImpl schema = (SchemaImpl) peekObject();
528
529            schema.addElementModel(enterElement(elementName));
530            return;
531        }
532
533        unexpectedElement(elementName);
534    }
535
536    private void beginServicePoint(String elementName)
537    {
538        if (elementName.equals("parameters-schema"))
539        {
540            enterParametersSchema(elementName);
541            return;
542        }
543
544        // <service-point> allows an super-set of <implementation>.
545
546        beginImplementation(elementName);
547    }
548
549    /**
550     * begin outermost element, expect "module".
551     */
552    private void beginStart(String elementName)
553    {
554        if (!elementName.equals("module"))
555            throw new ApplicationRuntimeException(ParseMessages.notModule(
556                    elementName,
557                    getLocation()), getLocation(), null);
558
559        ModuleDescriptor md = new ModuleDescriptor(_resolver, _errorHandler);
560
561        push(elementName, md, STATE_MODULE);
562
563        checkAttributes();
564
565        md.setModuleId(getValidatedAttribute("id", MODULE_ID_PATTERN, "module-id-format"));
566        md.setVersion(getValidatedAttribute("version", VERSION_PATTERN, "version-format"));
567
568        String packageName = getAttribute("package");
569        if (packageName == null)
570            packageName = md.getModuleId();
571
572        md.setPackageName(packageName);
573
574        // And, this is what we ultimately return from the parse.
575
576        _moduleDescriptor = md;
577    }
578
579    protected void push(String elementName, Object object, int state)
580    {
581        if (object instanceof AnnotationHolder)
582            super.push(elementName, object, state, false);
583        else
584            super.push(elementName, object, state, true);
585    }
586
587    private ElementImpl buildLWDomElement(String elementName)
588    {
589        ElementImpl result = new ElementImpl();
590        result.setElementName(elementName);
591
592        Iterator i = _attributes.entrySet().iterator();
593        while (i.hasNext())
594        {
595            Map.Entry entry = (Map.Entry) i.next();
596
597            String name = (String) entry.getKey();
598            String value = (String) entry.getValue();
599
600            Attribute a = new AttributeImpl(name, value);
601
602            result.addAttribute(a);
603        }
604
605        return result;
606    }
607
608    private void checkAttributes()
609    {
610        checkAttributes(peekElementName());
611    }
612
613    /**
614     * Checks that only known attributes are specified. Checks that all required attribute are
615     * specified.
616     */
617    private void checkAttributes(String elementName)
618    {
619        Iterator i = _attributes.keySet().iterator();
620
621        ElementParseInfo epi = (ElementParseInfo) _elementParseInfo.get(elementName);
622
623        // A few elements have no attributes at all.
624
625        if (epi == null)
626        {
627            epi = new ElementParseInfo();
628            _elementParseInfo.put(elementName, epi);
629        }
630
631        // First, check that each attribute is in the set of expected attributes.
632
633        while (i.hasNext())
634        {
635            String name = (String) i.next();
636
637            if (!epi.isKnown(name))
638                _errorHandler.error(
639                        LOG,
640                        ParseMessages.unknownAttribute(name, getElementPath()),
641                        getLocation(),
642                        null);
643        }
644
645        // Now check that all required attributes have been specified.
646
647        i = epi.getRequiredNames();
648        while (i.hasNext())
649        {
650            String name = (String) i.next();
651
652            if (!_attributes.containsKey(name))
653                throw new ApplicationRuntimeException(ParseMessages.requiredAttribute(
654                        name,
655                        getElementPath(),
656                        getLocation()));
657        }
658
659    }
660
661    public void end(String elementName)
662    {
663        switch (getState())
664        {
665            case STATE_LWDOM:
666
667                endLWDom();
668                break;
669
670            case STATE_CONVERSION:
671
672                endConversion();
673                break;
674
675            case STATE_SCHEMA:
676
677                endSchema();
678                break;
679
680            default:
681
682                String content = peekContent();
683
684                if (content != null && (peekObject() instanceof AnnotationHolder))
685                    ((AnnotationHolder) peekObject()).setAnnotation(content);
686
687                break;
688        }
689
690        // Pop the top item off the stack.
691
692        pop();
693    }
694
695    private void endSchema()
696    {
697        SchemaImpl schema = (SchemaImpl) peekObject();
698
699        schema.setAnnotation(peekContent());
700
701        try
702        {
703            schema.validateKeyAttributes();
704        }
705        catch (ApplicationRuntimeException e)
706        {
707            _errorHandler.error(LOG, ParseMessages.invalidElementKeyAttribute(schema.getId(), e), e
708                    .getLocation(), e);
709        }
710    }
711
712    private void endConversion()
713    {
714        ConversionDescriptor cd = (ConversionDescriptor) peekObject();
715
716        cd.addRulesForModel();
717    }
718
719    private void endLWDom()
720    {
721        ElementImpl element = (ElementImpl) peekObject();
722        element.setContent(peekContent());
723    }
724
725    private void enterAttribute(String elementName)
726    {
727        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
728
729        AttributeModelImpl attributeModel = new AttributeModelImpl();
730
731        push(elementName, attributeModel, STATE_NO_CONTENT);
732
733        checkAttributes();
734
735        attributeModel.setName(getAttribute("name"));
736        attributeModel.setRequired(getBooleanAttribute("required", false));
737        attributeModel.setUnique(getBooleanAttribute("unique", false));
738        attributeModel.setTranslator(getAttribute("translator", "smart"));
739
740        elementModel.addAttributeModel(attributeModel);
741    }
742
743    private void enterConfigurationPoint(String elementName)
744    {
745        ModuleDescriptor md = (ModuleDescriptor) peekObject();
746
747        ConfigurationPointDescriptor cpd = new ConfigurationPointDescriptor();
748
749        push(elementName, cpd, STATE_CONFIGURATION_POINT);
750
751        checkAttributes();
752
753        cpd.setId(getValidatedAttribute("id", ID_PATTERN, "id-format"));
754
755        Occurances count = (Occurances) getEnumAttribute("occurs", OCCURS_MAP);
756
757        if (count != null)
758            cpd.setCount(count);
759
760        Visibility visibility = (Visibility) getEnumAttribute("visibility", VISIBILITY_MAP);
761
762        if (visibility != null)
763            cpd.setVisibility(visibility);
764
765        cpd.setContributionsSchemaId(getAttribute("schema-id"));
766
767        md.addConfigurationPoint(cpd);
768    }
769
770    private void enterContribution(String elementName)
771    {
772        ModuleDescriptor md = (ModuleDescriptor) peekObject();
773
774        ContributionDescriptor cd = new ContributionDescriptor();
775
776        push(elementName, cd, STATE_CONTRIBUTION);
777
778        checkAttributes();
779
780        cd.setConfigurationId(getAttribute("configuration-id"));
781        cd.setConditionalExpression(getAttribute("if"));
782
783        md.addContribution(cd);
784    }
785
786    private void enterConversion(String elementName)
787    {
788        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
789
790        ConversionDescriptor cd = new ConversionDescriptor(_errorHandler, elementModel);
791
792        push(elementName, cd, STATE_CONVERSION);
793
794        checkAttributes();
795
796        cd.setClassName(getAttribute("class"));
797
798        String methodName = getAttribute("parent-method");
799
800        if (methodName != null)
801            cd.setParentMethodName(methodName);
802
803        elementModel.addRule(cd);
804    }
805
806    private void enterCreateInstance(String elementName)
807    {
808        AbstractServiceDescriptor sd = (AbstractServiceDescriptor) peekObject();
809        CreateInstanceDescriptor cid = new CreateInstanceDescriptor();
810
811        push(elementName, cid, STATE_CREATE_INSTANCE);
812
813        checkAttributes();
814
815        cid.setInstanceClassName(getAttribute("class"));
816
817        String model = getAttribute("model", DEFAULT_SERVICE_MODEL);
818
819        cid.setServiceModel(model);
820
821        sd.setInstanceBuilder(cid);
822
823    }
824
825    private void enterCreateObject(String elementName)
826    {
827        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
828        CreateObjectRule rule = new CreateObjectRule();
829        push(elementName, rule, STATE_NO_CONTENT);
830
831        checkAttributes();
832
833        rule.setClassName(getAttribute("class"));
834
835        elementModel.addRule(rule);
836    }
837
838    private void enterCustom(String elementName)
839    {
840        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
841
842        // Don't know what it is going to be, yet.
843
844        push(elementName, null, STATE_NO_CONTENT);
845
846        checkAttributes();
847
848        String ruleClassName = getAttribute("class");
849
850        Rule rule = getCustomRule(ruleClassName);
851
852        elementModel.addRule(rule);
853    }
854
855    /**
856     * Pushes STATE_ELEMENT onto the stack and creates and returns the {@link ElementModelImpl} it
857     * creates.
858     */
859    private ElementModel enterElement(String elementName)
860    {
861        ElementModelImpl result = new ElementModelImpl();
862
863        push(elementName, result, STATE_ELEMENT);
864
865        checkAttributes();
866
867        result.setElementName(getAttribute("name"));
868        result.setKeyAttribute(getAttribute("key-attribute"));
869        result.setContentTranslator(getAttribute("content-translator"));
870
871        return result;
872    }
873
874    private void enterEmbeddedConfigurationPointSchema(String elementName)
875    {
876        ConfigurationPointDescriptor cpd = (ConfigurationPointDescriptor) peekObject();
877
878        SchemaImpl schema = new SchemaImpl();
879
880        push(elementName, schema, STATE_SCHEMA);
881
882        if (cpd.getContributionsSchemaId() != null)
883        {
884            cpd.setContributionsSchemaId(null);
885            cpd.setContributionsSchema(schema);
886            _errorHandler.error(LOG, ParseMessages.multipleContributionsSchemas(cpd.getId(), schema
887                    .getLocation()), schema.getLocation(), null);
888        }
889        else
890            cpd.setContributionsSchema(schema);
891
892        checkAttributes("schema{embedded}");
893    }
894
895    private void enterParametersSchema(String elementName)
896    {
897        ServicePointDescriptor spd = (ServicePointDescriptor) peekObject();
898        SchemaImpl schema = new SchemaImpl();
899
900        push(elementName, schema, STATE_SCHEMA);
901
902        checkAttributes();
903
904        if (spd.getParametersSchemaId() != null)
905        {
906            spd.setParametersSchemaId(null);
907            spd.setParametersSchema(schema);
908            _errorHandler.error(LOG, ParseMessages.multipleParametersSchemas(spd.getId(), schema
909                    .getLocation()), schema.getLocation(), null);
910        }
911        else
912            spd.setParametersSchema(schema);
913    }
914
915    private void enterImplementation(String elementName)
916    {
917        ModuleDescriptor md = (ModuleDescriptor) peekObject();
918
919        ImplementationDescriptor id = new ImplementationDescriptor();
920
921        push(elementName, id, STATE_IMPLEMENTATION);
922
923        checkAttributes();
924
925        id.setServiceId(getAttribute("service-id"));
926        id.setConditionalExpression(getAttribute("if"));
927
928        md.addImplementation(id);
929    }
930
931    private void enterInterceptor(String elementName)
932    {
933        AbstractServiceDescriptor sd = (AbstractServiceDescriptor) peekObject();
934        InterceptorDescriptor id = new InterceptorDescriptor();
935
936        push(elementName, id, STATE_COLLECT_SERVICE_PARAMETERS);
937
938        checkAttributes();
939
940        id.setFactoryServiceId(getAttribute("service-id"));
941
942        id.setBefore(getAttribute("before"));
943        id.setAfter(getAttribute("after"));
944        id.setName(getAttribute("name"));
945        sd.addInterceptor(id);
946
947    }
948
949    private void enterInvokeFactory(String elementName)
950    {
951        AbstractServiceDescriptor sd = (AbstractServiceDescriptor) peekObject();
952        InvokeFactoryDescriptor ifd = new InvokeFactoryDescriptor();
953
954        push(elementName, ifd, STATE_COLLECT_SERVICE_PARAMETERS);
955
956        checkAttributes();
957
958        ifd.setFactoryServiceId(getAttribute("service-id", "hivemind.BuilderFactory"));
959
960        String model = getAttribute("model", DEFAULT_SERVICE_MODEL);
961
962        ifd.setServiceModel(model);
963
964        // TODO: Check if instanceBuilder already set
965
966        sd.setInstanceBuilder(ifd);
967
968    }
969
970    private void enterInvokeParent(String elementName)
971    {
972        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
973        InvokeParentRule rule = new InvokeParentRule();
974
975        push(elementName, rule, STATE_NO_CONTENT);
976
977        checkAttributes();
978
979        rule.setMethodName(getAttribute("method"));
980
981        if (_attributes.containsKey("depth"))
982            rule.setDepth(getIntAttribute("depth"));
983
984        elementModel.addRule(rule);
985    }
986
987    private void enterReadAttribute(String elementName)
988    {
989        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
990        ReadAttributeRule rule = new ReadAttributeRule();
991
992        push(elementName, rule, STATE_NO_CONTENT);
993
994        checkAttributes();
995
996        rule.setPropertyName(getAttribute("property"));
997        rule.setAttributeName(getAttribute("attribute"));
998        rule.setSkipIfNull(getBooleanAttribute("skip-if-null", true));
999        rule.setTranslator(getAttribute("translator"));
1000
1001        elementModel.addRule(rule);
1002    }
1003
1004    private void enterReadContent(String elementName)
1005    {
1006        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
1007        ReadContentRule rule = new ReadContentRule();
1008
1009        push(elementName, rule, STATE_NO_CONTENT);
1010
1011        checkAttributes();
1012
1013        rule.setPropertyName(getAttribute("property"));
1014
1015        elementModel.addRule(rule);
1016    }
1017
1018    private void enterRules(String elementName)
1019    {
1020        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
1021
1022        push(elementName, elementModel, STATE_RULES);
1023
1024    }
1025
1026    private void enterSchema(String elementName)
1027    {
1028        SchemaImpl schema = new SchemaImpl();
1029
1030        push(elementName, schema, STATE_SCHEMA);
1031
1032        checkAttributes();
1033
1034        String id = getValidatedAttribute("id", ID_PATTERN, "id-format");
1035
1036        schema.setId(id);
1037
1038        Visibility visibility = (Visibility) getEnumAttribute("visibility", VISIBILITY_MAP);
1039
1040        if (visibility != null)
1041            schema.setVisibility(visibility);
1042
1043        _moduleDescriptor.addSchema(schema);
1044    }
1045
1046    private void enterServicePoint(String elementName)
1047    {
1048        ModuleDescriptor md = (ModuleDescriptor) peekObject();
1049
1050        ServicePointDescriptor spd = new ServicePointDescriptor();
1051
1052        push(elementName, spd, STATE_SERVICE_POINT);
1053
1054        checkAttributes();
1055
1056        String id = getValidatedAttribute("id", ID_PATTERN, "id-format");
1057
1058        // Get the interface name, and default it to the service id if omitted.
1059
1060        String interfaceAttribute = getAttribute("interface", id);
1061
1062        // Qualify the interface name with the defined package name (which will
1063        // often implicitly or explicitly match the module id).
1064
1065        String interfaceName = IdUtils.qualify(
1066                _moduleDescriptor.getPackageName(),
1067                interfaceAttribute);
1068
1069        spd.setId(id);
1070
1071        spd.setInterfaceClassName(interfaceName);
1072
1073        spd.setParametersSchemaId(getAttribute("parameters-schema-id"));
1074
1075        Occurances count = (Occurances) getEnumAttribute("parameters-occurs", OCCURS_MAP);
1076
1077        if (count != null)
1078            spd.setParametersCount(count);
1079
1080        Visibility visibility = (Visibility) getEnumAttribute("visibility", VISIBILITY_MAP);
1081
1082        if (visibility != null)
1083            spd.setVisibility(visibility);
1084
1085        md.addServicePoint(spd);
1086    }
1087
1088    private void enterSetModule(String elementName)
1089    {
1090        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
1091        SetModuleRule rule = new SetModuleRule();
1092
1093        push(elementName, rule, STATE_NO_CONTENT);
1094
1095        checkAttributes();
1096
1097        rule.setPropertyName(getAttribute("property"));
1098
1099        elementModel.addRule(rule);
1100    }
1101
1102    private void enterSetParent(String elementName)
1103    {
1104        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
1105        SetParentRule rule = new SetParentRule();
1106
1107        push(elementName, rule, STATE_NO_CONTENT);
1108
1109        checkAttributes();
1110
1111        rule.setPropertyName(getAttribute("property"));
1112
1113        elementModel.addRule(rule);
1114    }
1115
1116    private void enterSetProperty(String elementName)
1117    {
1118        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
1119
1120        SetPropertyRule rule = new SetPropertyRule();
1121
1122        push(elementName, rule, STATE_NO_CONTENT);
1123
1124        checkAttributes();
1125
1126        rule.setPropertyName(getAttribute("property"));
1127        rule.setValue(getAttribute("value"));
1128
1129        elementModel.addRule(rule);
1130    }
1131
1132    private void enterPushAttribute(String elementName)
1133    {
1134        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
1135
1136        PushAttributeRule rule = new PushAttributeRule();
1137
1138        push(elementName, rule, STATE_NO_CONTENT);
1139
1140        checkAttributes();
1141
1142        rule.setAttributeName(getAttribute("attribute"));
1143
1144        elementModel.addRule(rule);
1145    }
1146
1147    private void enterPushContent(String elementName)
1148    {
1149        ElementModelImpl elementModel = (ElementModelImpl) peekObject();
1150
1151        PushContentRule rule = new PushContentRule();
1152
1153        push(elementName, rule, STATE_NO_CONTENT);
1154
1155        checkAttributes();
1156
1157        elementModel.addRule(rule);
1158    }
1159
1160    private void enterSubModule(String elementName)
1161    {
1162        ModuleDescriptor md = (ModuleDescriptor) peekObject();
1163
1164        SubModuleDescriptor smd = new SubModuleDescriptor();
1165
1166        push(elementName, smd, STATE_NO_CONTENT);
1167
1168        checkAttributes();
1169
1170        Resource descriptor = getResource().getRelativeResource(getAttribute("descriptor"));
1171
1172        smd.setDescriptor(descriptor);
1173
1174        md.addSubModule(smd);
1175    }
1176
1177    private void enterDependency(String elementName)
1178    {
1179        ModuleDescriptor md = (ModuleDescriptor) peekObject();
1180
1181        DependencyDescriptor dd = new DependencyDescriptor();
1182
1183        push(elementName, dd, STATE_NO_CONTENT);
1184
1185        checkAttributes();
1186
1187        dd.setModuleId(getAttribute("module-id"));
1188        dd.setVersion(getAttribute("version"));
1189
1190        md.addDependency(dd);
1191    }
1192
1193    private String getAttribute(String name)
1194    {
1195        return (String) _attributes.get(name);
1196    }
1197
1198    private String getAttribute(String name, String defaultValue)
1199    {
1200        String result = (String) _attributes.get(name);
1201
1202        if (result == null)
1203            result = defaultValue;
1204
1205        return result;
1206    }
1207
1208    private String getValidatedAttribute(String name, String pattern, String formatKey)
1209    {
1210        String result = getAttribute(name);
1211
1212        if (!validateFormat(result, pattern))
1213            _errorHandler.error(LOG, ParseMessages.invalidAttributeFormat(
1214                    name,
1215                    result,
1216                    getElementPath(),
1217                    formatKey), getLocation(), null);
1218
1219        return result;
1220    }
1221
1222    private boolean validateFormat(String input, String pattern)
1223    {
1224        if (_compiler == null)
1225        {
1226            _compiler = new Perl5Compiler();
1227            _matcher = new Perl5Matcher();
1228            _compiledPatterns = new HashMap();
1229        }
1230
1231        Pattern compiled = (Pattern) _compiledPatterns.get(pattern);
1232        if (compiled == null)
1233        {
1234
1235            try
1236            {
1237                compiled = _compiler.compile(pattern);
1238            }
1239            catch (MalformedPatternException ex)
1240            {
1241                throw new ApplicationRuntimeException(ex);
1242            }
1243
1244            _compiledPatterns.put(pattern, compiled);
1245        }
1246
1247        return _matcher.matches(input, compiled);
1248    }
1249
1250    private boolean getBooleanAttribute(String name, boolean defaultValue)
1251    {
1252        String value = getAttribute(name);
1253
1254        if (value == null)
1255            return defaultValue;
1256
1257        if (value.equals("true"))
1258            return true;
1259
1260        if (value.equals("false"))
1261            return false;
1262
1263        _errorHandler.error(
1264                LOG,
1265                ParseMessages.booleanAttribute(value, name, getElementPath()),
1266                getLocation(),
1267                null);
1268
1269        return defaultValue;
1270    }
1271
1272    private Rule getCustomRule(String ruleClassName)
1273    {
1274        Rule result = (Rule) _ruleMap.get(ruleClassName);
1275
1276        if (result == null)
1277        {
1278            result = instantiateRule(ruleClassName);
1279
1280            _ruleMap.put(ruleClassName, result);
1281        }
1282
1283        return result;
1284    }
1285
1286    /**
1287     * Gets the value for the attribute and uses the Map to translate it to an object value. Returns
1288     * the object value if succesfully translated. Returns null if unsuccesful. If a value is
1289     * provided that isn't a key of the map, and error is logged and null is returned.
1290     */
1291    private Object getEnumAttribute(String name, Map translations)
1292    {
1293        String value = getAttribute(name);
1294
1295        if (value == null)
1296            return null;
1297
1298        Object result = translations.get(value);
1299
1300        if (result == null)
1301            _errorHandler.error(LOG, ParseMessages.invalidAttributeValue(
1302                    value,
1303                    name,
1304                    getElementPath()), getLocation(), null);
1305
1306        return result;
1307    }
1308
1309    private int getIntAttribute(String name)
1310    {
1311        String value = getAttribute(name);
1312
1313        try
1314        {
1315            return Integer.parseInt(value);
1316        }
1317        catch (NumberFormatException ex)
1318        {
1319            _errorHandler.error(LOG, ParseMessages.invalidNumericValue(
1320                    value,
1321                    name,
1322                    getElementPath()), getLocation(), ex);
1323
1324            return 0;
1325        }
1326    }
1327
1328    private void initializeFromProperties(Properties p)
1329    {
1330        Enumeration e = p.propertyNames();
1331
1332        while (e.hasMoreElements())
1333        {
1334            String key = (String) e.nextElement();
1335            String value = p.getProperty(key);
1336
1337            initializeFromProperty(key, value);
1338        }
1339    }
1340
1341    /**
1342     * Invoked from the constructor to read the properties file that defines certain aspects of the
1343     * operation of the parser.
1344     */
1345    private void initializeFromPropertiesFile()
1346    {
1347        Properties p = new Properties();
1348
1349        try
1350        {
1351
1352            InputStream propertiesIn = getClass()
1353                    .getResourceAsStream("DescriptorParser.properties");
1354            InputStream bufferedIn = new BufferedInputStream(propertiesIn);
1355
1356            p.load(bufferedIn);
1357
1358            bufferedIn.close();
1359        }
1360        catch (IOException ex)
1361        {
1362            _errorHandler.error(LOG, ParseMessages.unableToInitialize(ex), null, ex);
1363        }
1364
1365        initializeFromProperties(p);
1366    }
1367
1368    private void initializeFromProperty(String key, String value)
1369    {
1370        if (key.startsWith("required."))
1371        {
1372            initializeRequired(key, value);
1373            return;
1374        }
1375
1376    }
1377
1378    private void initializeRequired(String key, String value)
1379    {
1380        boolean required = value.equals("true");
1381
1382        int lastdotx = key.lastIndexOf('.');
1383
1384        String elementName = key.substring(9, lastdotx);
1385        String attributeName = key.substring(lastdotx + 1);
1386
1387        ElementParseInfo epi = (ElementParseInfo) _elementParseInfo.get(elementName);
1388
1389        if (epi == null)
1390        {
1391            epi = new ElementParseInfo();
1392            _elementParseInfo.put(elementName, epi);
1393        }
1394
1395        epi.addAttribute(attributeName, required);
1396    }
1397
1398    private Rule instantiateRule(String ruleClassName)
1399    {
1400        try
1401        {
1402            Class ruleClass = _resolver.findClass(ruleClassName);
1403
1404            return (Rule) ruleClass.newInstance();
1405        }
1406        catch (Exception ex)
1407        {
1408            throw new ApplicationRuntimeException(ParseMessages.badRuleClass(
1409                    ruleClassName,
1410                    getLocation(),
1411                    ex), getLocation(), ex);
1412        }
1413    }
1414
1415    /** @since 1.1 */
1416    public void initialize(Resource resource, ClassResolver resolver)
1417    {
1418        initializeParser(resource, STATE_START);
1419
1420        _resolver = resolver;
1421    }
1422
1423    /** @since 1.1 */
1424    public ModuleDescriptor getModuleDescriptor()
1425    {
1426        return _moduleDescriptor;
1427    }
1428
1429    /** @since 1.1 */
1430    public void reset()
1431    {
1432        super.resetParser();
1433
1434        _moduleDescriptor = null;
1435        _attributes.clear();
1436        _resolver = null;
1437    }
1438}