1 package org.apache.fulcrum.intake;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.beans.IntrospectionException;
23 import java.beans.PropertyDescriptor;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.ObjectInputStream;
29 import java.io.ObjectOutputStream;
30 import java.lang.reflect.Method;
31 import java.net.URL;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.List;
36 import java.util.ListIterator;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 import java.util.Set;
40
41 import javax.xml.XMLConstants;
42 import javax.xml.bind.JAXBContext;
43 import javax.xml.bind.Unmarshaller;
44 import javax.xml.bind.Unmarshaller.Listener;
45 import javax.xml.bind.helpers.DefaultValidationEventHandler;
46 import javax.xml.validation.SchemaFactory;
47
48 import org.apache.avalon.framework.activity.Initializable;
49 import org.apache.avalon.framework.configuration.Configurable;
50 import org.apache.avalon.framework.configuration.Configuration;
51 import org.apache.avalon.framework.configuration.ConfigurationException;
52 import org.apache.avalon.framework.context.Context;
53 import org.apache.avalon.framework.context.ContextException;
54 import org.apache.avalon.framework.context.Contextualizable;
55 import org.apache.avalon.framework.logger.AbstractLogEnabled;
56 import org.apache.avalon.framework.logger.LogEnabled;
57 import org.apache.avalon.framework.service.ServiceException;
58 import org.apache.avalon.framework.service.ServiceManager;
59 import org.apache.avalon.framework.service.Serviceable;
60 import org.apache.commons.pool2.KeyedObjectPool;
61 import org.apache.commons.pool2.KeyedPooledObjectFactory;
62 import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
63 import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
64 import org.apache.fulcrum.intake.model.AppData;
65 import org.apache.fulcrum.intake.model.Field;
66 import org.apache.fulcrum.intake.model.Group;
67
68
69
70
71
72
73
74
75
76
77
78
79
80 public class IntakeServiceImpl extends AbstractLogEnabled implements
81 IntakeService, Configurable, Initializable, Contextualizable,
82 Serviceable
83 {
84
85 private Map<String, AppData> groupNames;
86
87
88 private Map<String, String> groupNameMap;
89
90
91 private Map<String, String> groupKeyMap;
92
93
94 private Map<String, Map<String, Method>> getterMap;
95
96
97 private Map<String, Map<String, Method>> setterMap;
98
99
100 private Map<AppData, KeyedObjectPool<String, Group>> keyedPools;
101
102
103 private String applicationRoot;
104
105
106 private List<String> xmlPathes = null;
107
108
109 private String serialDataPath = null;
110
111
112
113
114
115 private class AvalonLogEnabledListener extends Listener
116 {
117
118
119
120 @Override
121 public void beforeUnmarshal(Object target, Object parent)
122 {
123 super.beforeUnmarshal(target, parent);
124
125 if (target instanceof LogEnabled)
126 {
127 ((LogEnabled)target).enableLogging(getLogger());
128 }
129 }
130
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147 private boolean registerGroup(String groupName, Group group,
148 AppData appData, boolean checkKey)
149 {
150 if (groupNames.containsKey(groupName))
151 {
152
153 return false;
154 }
155
156 boolean keyExists = groupNameMap.containsKey(group.getGID());
157
158 if (checkKey && keyExists)
159 {
160
161 return false;
162 }
163
164 groupNames.put(groupName, appData);
165 groupKeyMap.put(groupName, group.getGID());
166
167 if (!keyExists)
168 {
169
170 groupNameMap.put(group.getGID(), groupName);
171 }
172
173 List<Field<?>> fields = group.getFields();
174 for (Field<?> field : fields)
175 {
176 String className = field.getMapToObject();
177 if (!getterMap.containsKey(className))
178 {
179 getterMap.put(className, new HashMap<String, Method>());
180 setterMap.put(className, new HashMap<String, Method>());
181 }
182 }
183 return true;
184 }
185
186
187
188
189
190
191
192
193
194
195
196 private Map<AppData, File> loadSerialized(String serialDataPath, long timeStamp)
197 {
198 getLogger().debug(
199 "Entered loadSerialized(" + serialDataPath + ", " + timeStamp
200 + ")");
201
202 long timer = System.currentTimeMillis();
203
204 if (serialDataPath == null)
205 {
206 return null;
207 }
208
209 File serialDataFile = new File(serialDataPath);
210
211 if (!serialDataFile.exists())
212 {
213 getLogger().info("No serialized file found, parsing XML");
214 return null;
215 }
216
217 if (serialDataFile.lastModified() <= timeStamp)
218 {
219 getLogger().info("serialized file too old, parsing XML");
220 return null;
221 }
222
223 ObjectInputStream in = null;
224 Map<AppData, File> serialData = null;
225
226 try
227 {
228 FileInputStream fin = new FileInputStream(serialDataFile);
229 in = new ObjectInputStream(fin);
230 Object o = in.readObject();
231
232 if (o instanceof Map)
233 {
234 @SuppressWarnings("unchecked")
235 Map<AppData, File> map = (Map<AppData, File>) o;
236 serialData = map;
237 }
238 else
239 {
240
241 getLogger().info("serialized object is not an intake map, ignoring");
242 in.close();
243 in = null;
244
245
246 boolean result = serialDataFile.delete();
247 if ( result == false )
248 {
249 getLogger().error("Unknown serialized file could not be removed");
250 }
251 }
252 }
253 catch (IOException e)
254 {
255 getLogger().error("Serialized File could not be read.", e);
256
257
258
259 serialData = null;
260 }
261 catch (ClassNotFoundException e)
262 {
263 getLogger().error("Objects could not be read from serialized file.", e);
264
265
266
267 serialData = null;
268 }
269 finally
270 {
271
272
273 try
274 {
275 if (in != null)
276 {
277 in.close();
278 }
279 }
280 catch (IOException e)
281 {
282 getLogger().error("Exception while closing file", e);
283 }
284 }
285
286
287 if (serialData != null)
288 {
289 for (AppData appData : serialData.keySet())
290 {
291 for (Group group : appData.getGroups())
292 {
293 if (group instanceof LogEnabled)
294 {
295 ((LogEnabled)group).enableLogging(getLogger());
296 }
297
298 for (Field<?> field : group.getFields())
299 {
300 if (field instanceof LogEnabled)
301 {
302 ((LogEnabled)field).enableLogging(getLogger());
303 }
304 }
305 }
306 }
307 }
308 getLogger().info("Loaded serialized map object, ignoring XML");
309 getLogger().debug("Loading took " + (System.currentTimeMillis() - timer));
310 return serialData;
311 }
312
313
314
315
316
317
318
319
320
321
322
323 private void saveSerialized(String serialDataPath, Map<AppData, File> appDataElements)
324 {
325
326 getLogger().debug(
327 "Entered saveSerialized(" + serialDataPath
328 + ", appDataElements)");
329
330 long timer = System.currentTimeMillis();
331
332 if (serialDataPath == null)
333 {
334 return;
335 }
336
337 File serialData = new File(serialDataPath);
338
339 try
340 {
341 boolean result = serialData.createNewFile();
342 if ( result == false )
343 {
344 getLogger().error("Could not create new serialized file");
345 }
346
347
348 result = serialData.delete();
349 if ( result == false )
350 {
351 getLogger().error("Serialized file could not be removed");
352 }
353
354 }
355 catch (IOException e)
356 {
357 getLogger().info(
358 "Could not create serialized file " + serialDataPath
359 + ", not serializing the XML data", e);
360 return;
361 }
362
363 ObjectOutputStream out = null;
364 ObjectInputStream in = null;
365
366 try
367 {
368
369 FileOutputStream fout = new FileOutputStream(serialDataPath);
370 out = new ObjectOutputStream(fout);
371 out.writeObject(appDataElements);
372 out.flush();
373
374
375
376 FileInputStream fin = new FileInputStream(serialDataPath);
377 in = new ObjectInputStream(fin);
378 in.readObject();
379
380 getLogger().debug("Serializing successful");
381 }
382 catch (IOException e)
383 {
384 getLogger().info(
385 "Could not write serialized file to " + serialDataPath
386 + ", not serializing the XML data", e);
387 }
388 catch (ClassNotFoundException e)
389 {
390 getLogger().info(
391 "Could not re-read serialized file from " + serialDataPath, e);
392 }
393 finally
394 {
395 try
396 {
397 if (out != null)
398 {
399 out.close();
400 }
401 }
402 catch (IOException e)
403 {
404 getLogger().error("Exception while closing file", e);
405 }
406 try
407 {
408 if (in != null)
409 {
410 in.close();
411 }
412 }
413 catch (IOException e)
414 {
415 getLogger().error("Exception while closing file", e);
416 }
417 }
418
419 getLogger().debug("Saving took " + (System.currentTimeMillis() - timer));
420 }
421
422
423
424
425
426
427
428
429
430
431
432 @Override
433 public Group getGroup(String groupName) throws IntakeException
434 {
435 Group group = null;
436
437 AppData appData = groupNames.get(groupName);
438
439 if (groupName == null)
440 {
441 throw new IntakeException(
442 "Intake IntakeServiceImpl.getGroup(groupName) is null");
443 }
444
445 if (appData == null)
446 {
447 throw new IntakeException(
448 "Intake IntakeServiceImpl.getGroup(groupName): No XML definition for Group "
449 + groupName + " found");
450 }
451 try
452 {
453 group = keyedPools.get(appData).borrowObject(groupName);
454 }
455 catch (Exception e)
456 {
457 throw new IntakeException("Could not get group " + groupName, e);
458 }
459 return group;
460 }
461
462
463
464
465
466
467
468
469
470
471 @Override
472 public void releaseGroup(Group instance) throws IntakeException
473 {
474 if (instance != null)
475 {
476 String groupName = instance.getIntakeGroupName();
477 AppData appData = groupNames.get(groupName);
478
479 if (appData == null)
480 {
481 throw new IntakeException(
482 "Intake IntakeServiceImpl.releaseGroup(groupName): "
483 + "No XML definition for Group " + groupName
484 + " found");
485 }
486
487 try
488 {
489 keyedPools.get(appData).returnObject(groupName, instance);
490 }
491 catch (Exception e)
492 {
493 throw new IntakeException("Could not get group " + groupName, e);
494 }
495 }
496 }
497
498
499
500
501
502
503
504
505
506
507 @Override
508 public int getSize(String groupName) throws IntakeException
509 {
510 AppData appData = groupNames.get(groupName);
511 if (appData == null)
512 {
513 throw new IntakeException(
514 "Intake IntakeServiceImpl.Size(groupName): No XML definition for Group "
515 + groupName + " found");
516 }
517
518 KeyedObjectPool<String, Group> kop = keyedPools.get(appData);
519
520 return kop.getNumActive(groupName) + kop.getNumIdle(groupName);
521 }
522
523
524
525
526
527
528 @Override
529 public String[] getGroupNames()
530 {
531 return groupNames.keySet().toArray(new String[0]);
532 }
533
534
535
536
537
538
539
540
541 @Override
542 public String getGroupKey(String groupName)
543 {
544 return groupKeyMap.get(groupName);
545 }
546
547
548
549
550
551
552
553
554 @Override
555 public String getGroupName(String groupKey)
556 {
557 return groupNameMap.get(groupKey);
558 }
559
560
561
562
563
564
565
566
567
568
569
570
571 @Override
572 public Method getFieldSetter(String className, String propName)
573 throws ClassNotFoundException, IntrospectionException
574 {
575 Map<String, Method> settersForClassName = setterMap.get(className);
576
577 if (settersForClassName == null)
578 {
579 throw new IntrospectionException("No setter Map for " + className
580 + " available!");
581 }
582
583 Method setter = settersForClassName.get(propName);
584
585 if (setter == null)
586 {
587 PropertyDescriptor pd = new PropertyDescriptor(propName, Class
588 .forName(className));
589 synchronized (setterMap)
590 {
591 setter = pd.getWriteMethod();
592 settersForClassName.put(propName, setter);
593 if (setter == null)
594 {
595 getLogger().error(
596 "Intake: setter for '" + propName + "' in class '"
597 + className + "' could not be found.");
598 }
599 }
600
601
602 synchronized (getterMap)
603 {
604 Map<String, Method> gettersForClassName = getterMap.get(className);
605
606 if (gettersForClassName != null)
607 {
608 Method getter = pd.getReadMethod();
609 if (getter != null)
610 {
611 gettersForClassName.put(propName, getter);
612 }
613 }
614 }
615 }
616 return setter;
617 }
618
619
620
621
622
623
624
625
626
627
628
629
630 @Override
631 public Method getFieldGetter(String className, String propName)
632 throws ClassNotFoundException, IntrospectionException
633 {
634 Map<String, Method> gettersForClassName = getterMap.get(className);
635
636 if (gettersForClassName == null)
637 {
638 throw new IntrospectionException("No getter Map for " + className
639 + " available!");
640 }
641
642 Method getter = gettersForClassName.get(propName);
643
644 if (getter == null)
645 {
646 PropertyDescriptor pd = null;
647 synchronized (getterMap)
648 {
649 pd = new PropertyDescriptor(propName, Class.forName(className));
650 getter = pd.getReadMethod();
651 gettersForClassName.put(propName, getter);
652 if (getter == null)
653 {
654 getLogger().error(
655 "Intake: getter for '" + propName + "' in class '"
656 + className + "' could not be found.");
657 }
658 }
659
660
661 synchronized (setterMap)
662 {
663 Map<String, Method> settersForClassName = getterMap.get(className);
664
665 if (settersForClassName != null)
666 {
667 Method setter = pd.getWriteMethod();
668 if (setter != null)
669 {
670 settersForClassName.put(propName, setter);
671 }
672 }
673 }
674 }
675 return getter;
676 }
677
678
679
680
681
682 @Override
683 public void configure(Configuration conf) throws ConfigurationException
684 {
685 final Configuration xmlPaths = conf.getChild(XML_PATHS, false);
686
687 xmlPathes = new ArrayList<String>();
688
689 if (xmlPaths == null)
690 {
691 xmlPathes.add(XML_PATH_DEFAULT);
692 }
693 else
694 {
695 Configuration[] nameVal = xmlPaths.getChildren();
696 for (int i = 0; i < nameVal.length; i++)
697 {
698 String val = nameVal[i].getValue();
699 xmlPathes.add(val);
700 }
701 }
702
703 serialDataPath = conf.getChild(SERIAL_XML, false).getValue(SERIAL_XML_DEFAULT);
704
705 if (!serialDataPath.equalsIgnoreCase("none"))
706 {
707 serialDataPath = new File(applicationRoot, serialDataPath).getAbsolutePath();
708 }
709 else
710 {
711 serialDataPath = null;
712 }
713
714 getLogger().debug("Path for serializing: " + serialDataPath);
715 }
716
717
718
719
720
721
722
723
724 @Override
725 public void initialize() throws Exception
726 {
727 Map<AppData, File> appDataElements = null;
728
729 groupNames = new HashMap<String, AppData>();
730 groupKeyMap = new HashMap<String, String>();
731 groupNameMap = new HashMap<String, String>();
732 getterMap = new HashMap<String, Map<String,Method>>();
733 setterMap = new HashMap<String, Map<String,Method>>();
734 keyedPools = new HashMap<AppData, KeyedObjectPool<String, Group>>();
735
736 Set<File> xmlFiles = new HashSet<File>();
737
738 long timeStamp = 0;
739
740 getLogger().debug("logger is " + getLogger().getClass().getSimpleName());
741
742 for (String xmlPath : xmlPathes)
743 {
744
745 File xmlFile = new File(applicationRoot, xmlPath).getAbsoluteFile();
746
747 getLogger().debug("Path for XML File: " + xmlFile);
748
749 if (!xmlFile.canRead())
750 {
751 String READ_ERR = "Could not read input file with path "
752 + xmlPath + ". Looking for file " + xmlFile;
753
754 getLogger().error(READ_ERR);
755 throw new Exception(READ_ERR);
756 }
757
758 xmlFiles.add(xmlFile);
759
760 getLogger().debug("Added " + xmlPath + " as File to parse");
761
762
763
764
765 timeStamp = xmlFile.lastModified() > timeStamp ? xmlFile
766 .lastModified() : timeStamp;
767 }
768
769 Map<AppData, File> serializedMap = loadSerialized(serialDataPath, timeStamp);
770
771 if (serializedMap != null)
772 {
773
774 appDataElements = serializedMap;
775 getLogger().debug("Using the serialized map");
776 }
777 else
778 {
779 long timer = System.currentTimeMillis();
780
781
782 JAXBContext jaxb = JAXBContext.newInstance(AppData.class);
783 Unmarshaller um = jaxb.createUnmarshaller();
784
785
786 um.setEventHandler(new DefaultValidationEventHandler());
787
788
789 Listener logEnabledListener = new AvalonLogEnabledListener();
790 um.setListener(logEnabledListener);
791
792 URL schemaURL = getClass().getResource("/intake.xsd");
793 SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
794 um.setSchema(schemaFactory.newSchema(schemaURL));
795 appDataElements = new HashMap<AppData, File>();
796
797 for (File xmlFile : xmlFiles)
798 {
799 getLogger().debug("Now parsing: " + xmlFile);
800 FileInputStream fis = null;
801 try
802 {
803 fis = new FileInputStream(xmlFile);
804 AppData/../../../org/apache/fulcrum/intake/model/AppData.html#AppData">AppData appData = (AppData) um.unmarshal(fis);
805
806 appDataElements.put(appData, xmlFile);
807 getLogger().debug("Saving AppData for " + xmlFile);
808 }
809 finally
810 {
811 if (fis != null)
812 {
813 try
814 {
815 fis.close();
816 }
817 catch (IOException e)
818 {
819 getLogger().warn("Could not close file " + xmlFile);
820 }
821 }
822 }
823 }
824
825 getLogger().debug("Parsing took " + (System.currentTimeMillis() - timer));
826 saveSerialized(serialDataPath, appDataElements);
827 }
828
829 int counter = 0;
830 AppData appData;
831 File dataFile;
832 for ( Entry<AppData, File> entry : appDataElements.entrySet() )
833 {
834
835 appData = entry.getKey();
836 dataFile = entry.getValue();
837
838 int maxPooledGroups = 0;
839 List<Group> glist = appData.getGroups();
840
841 String groupPrefix = appData.getGroupPrefix();
842
843 for (ListIterator<Group> i = glist.listIterator(glist.size()); i.hasPrevious();)
844 {
845 Group g = i.previous();
846 String groupName = g.getIntakeGroupName();
847
848 boolean registerUnqualified = registerGroup(groupName, g, appData, true);
849
850 if (!registerUnqualified)
851 {
852 getLogger().info(
853 "Ignored redefinition of Group " + groupName
854 + " or Key " + g.getGID() + " from "
855 + dataFile);
856 }
857
858 if (groupPrefix != null)
859 {
860 StringBuilder qualifiedName = new StringBuilder();
861 qualifiedName.append(groupPrefix).append(':').append(groupName);
862
863
864
865
866
867
868
869 if (!registerGroup(qualifiedName.toString(), g,
870 appData, !registerUnqualified))
871 {
872 getLogger().error(
873 "Could not register fully qualified name "
874 + qualifiedName
875 + ", maybe two XML files have the same prefix. Ignoring it.");
876 }
877 }
878
879
880 for (Field<?> f : g.getFields())
881 {
882 f.initGetterAndSetter();
883 }
884
885 maxPooledGroups = Math.max(maxPooledGroups, g.getPoolCapacity());
886 }
887
888 KeyedPooledObjectFactory<String, Group> factory =
889 new Group.GroupFactory(appData);
890
891 GenericKeyedObjectPoolConfig<Group> poolConfig = new GenericKeyedObjectPoolConfig<Group>();
892 poolConfig.setMaxTotalPerKey(maxPooledGroups);
893 poolConfig.setJmxEnabled(true);
894 poolConfig.setJmxNamePrefix("fulcrum-intake-pool-" + counter++);
895
896 keyedPools.put(appData,
897 new GenericKeyedObjectPool<String, Group>(factory, poolConfig));
898 }
899
900 if (getLogger().isInfoEnabled())
901 {
902 getLogger().info("Intake Service is initialized now.");
903 }
904 }
905
906
907
908
909
910
911
912
913
914
915 @Override
916 public void contextualize(Context context) throws ContextException
917 {
918 this.applicationRoot = context.get("urn:avalon:home").toString();
919 }
920
921
922
923
924
925
926
927
928
929
930
931 @Override
932 public void service(ServiceManager manager) throws ServiceException
933 {
934 IntakeServiceFacade.setIntakeService(this);
935 }
936 }