1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.tooling.commons.packmgr.unpack;
21
22 import static org.apache.jackrabbit.vault.util.Constants.DOT_CONTENT_XML;
23 import static org.apache.jackrabbit.vault.util.Constants.ROOT_DIR;
24
25 import java.io.File;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.math.BigDecimal;
31 import java.util.ArrayList;
32 import java.util.Calendar;
33 import java.util.Enumeration;
34 import java.util.HashSet;
35 import java.util.LinkedHashSet;
36 import java.util.List;
37 import java.util.Set;
38 import java.util.TreeSet;
39 import java.util.concurrent.atomic.AtomicBoolean;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
42 import java.util.regex.PatternSyntaxException;
43
44 import javax.jcr.Binary;
45 import javax.jcr.Item;
46 import javax.jcr.ItemVisitor;
47 import javax.jcr.Node;
48 import javax.jcr.Property;
49 import javax.jcr.PropertyType;
50 import javax.jcr.RepositoryException;
51 import javax.jcr.Session;
52 import javax.jcr.Value;
53 import javax.jcr.ValueFormatException;
54 import javax.jcr.nodetype.NodeType;
55 import javax.jcr.nodetype.PropertyDefinition;
56 import javax.xml.XMLConstants;
57 import javax.xml.parsers.ParserConfigurationException;
58 import javax.xml.parsers.SAXParser;
59 import javax.xml.parsers.SAXParserFactory;
60
61 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
62 import org.apache.commons.compress.archivers.zip.ZipFile;
63 import org.apache.commons.io.FileUtils;
64 import org.apache.commons.io.FilenameUtils;
65 import org.apache.commons.io.IOUtils;
66 import org.apache.commons.lang3.StringUtils;
67 import org.apache.jackrabbit.JcrConstants;
68 import org.apache.jackrabbit.util.ISO8601;
69 import org.apache.jackrabbit.vault.util.DocViewProperty;
70 import org.apache.jackrabbit.vault.util.PlatformNameFormat;
71 import org.jdom2.Attribute;
72 import org.jdom2.Document;
73 import org.jdom2.Element;
74 import org.jdom2.JDOMException;
75 import org.jdom2.Namespace;
76 import org.jdom2.input.SAXBuilder;
77 import org.jdom2.output.Format;
78 import org.jdom2.output.LineSeparator;
79 import org.jdom2.output.XMLOutputter;
80 import org.xml.sax.Attributes;
81 import org.xml.sax.SAXException;
82 import org.xml.sax.helpers.DefaultHandler;
83
84 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
85 import io.wcm.tooling.commons.packmgr.PackageManagerException;
86
87
88
89
90 public final class ContentUnpacker {
91
92 private static final String MIXINS_PROPERTY = "jcr:mixinTypes";
93 private static final String PRIMARYTYPE_PROPERTY = "jcr:primaryType";
94 private static final Namespace JCR_NAMESPACE = Namespace.getNamespace("jcr", "http://www.jcp.org/jcr/1.0");
95 private static final Namespace CQ_NAMESPACE = Namespace.getNamespace("cq", "http://www.day.com/jcr/cq/1.0");
96 private static final Pattern FILENAME_NAMESPACE_PATTERN = Pattern.compile("^([^:]+):(.+)$");
97
98 private static final SAXParserFactory SAX_PARSER_FACTORY;
99 static {
100 SAX_PARSER_FACTORY = SAXParserFactory.newInstance();
101 SAX_PARSER_FACTORY.setNamespaceAware(true);
102 }
103
104 private final Pattern[] excludeFiles;
105 private final Pattern[] excludeNodes;
106 private final Pattern[] excludeProperties;
107 private final Pattern[] excludeMixins;
108 private final boolean markReplicationActivated;
109 private final Pattern[] markReplicationActivatedIncludeNodes;
110 private final String dateLastReplicated;
111
112
113
114
115 public ContentUnpacker(ContentUnpackerProperties properties) {
116 this.excludeFiles = toPatternArray(properties.getExcludeFiles());
117 this.excludeNodes = toPatternArray(properties.getExcludeNodes());
118 this.excludeProperties = toPatternArray(properties.getExcludeProperties());
119 this.excludeMixins = toPatternArray(properties.getExcludeMixins());
120 this.markReplicationActivated = properties.isMarkReplicationActivated();
121 this.markReplicationActivatedIncludeNodes = toPatternArray(properties.getMarkReplicationActivatedIncludeNodes());
122
123 if (StringUtils.isNotBlank(properties.getDateLastReplicated())) {
124 this.dateLastReplicated = properties.getDateLastReplicated();
125 }
126 else {
127
128 Calendar cal = Calendar.getInstance();
129 cal.set(Calendar.HOUR_OF_DAY, 0);
130 cal.set(Calendar.MINUTE, 0);
131 cal.set(Calendar.SECOND, 0);
132 cal.set(Calendar.MILLISECOND, 0);
133 this.dateLastReplicated = ISO8601.format(cal);
134 }
135 }
136
137 private static Pattern[] toPatternArray(String[] patternStrings) {
138 if (patternStrings == null) {
139 return new Pattern[0];
140 }
141 Pattern[] patterns = new Pattern[patternStrings.length];
142 for (int i = 0; i < patternStrings.length; i++) {
143 try {
144 patterns[i] = Pattern.compile(patternStrings[i]);
145 }
146 catch (PatternSyntaxException ex) {
147 throw new PackageManagerException("Invalid regexp pattern: " + patternStrings[i], ex);
148 }
149 }
150 return patterns;
151 }
152
153 private static boolean matches(String name, Pattern[] patterns, boolean defaultIfNotPatternsDefined) {
154 if (patterns.length == 0) {
155 return defaultIfNotPatternsDefined;
156 }
157 for (Pattern pattern : patterns) {
158 if (pattern.matcher(name).matches()) {
159 return true;
160 }
161 }
162 return false;
163 }
164
165 private boolean applyXmlExcludes(String name) {
166 if (this.excludeNodes.length == 0 && this.excludeProperties.length == 0) {
167 return false;
168 }
169 return StringUtils.endsWith(name, ".xml");
170 }
171
172
173
174
175
176
177 public void unpack(File file, File outputDirectory) {
178 try (ZipFile zipFile = new ZipFile(file)) {
179 Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
180 while (entries.hasMoreElements()) {
181 ZipArchiveEntry entry = entries.nextElement();
182 if (!matches(entry.getName(), excludeFiles, false)) {
183 unpackEntry(zipFile, entry, outputDirectory);
184 }
185 }
186 }
187 catch (IOException ex) {
188 throw new PackageManagerException("Error reading content package " + file.getAbsolutePath(), ex);
189 }
190 }
191
192 @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE")
193 private void unpackEntry(ZipFile zipFile, ZipArchiveEntry entry, File outputDirectory) throws IOException {
194 if (entry.isDirectory()) {
195 File directory = FileUtils.getFile(outputDirectory, entry.getName());
196 directory.mkdirs();
197 }
198 else {
199 Set<String> namespacePrefixes = null;
200 if (applyXmlExcludes(entry.getName())) {
201 namespacePrefixes = getNamespacePrefixes(zipFile, entry);
202 }
203
204 try (InputStream entryStream = zipFile.getInputStream(entry)) {
205 File outputFile = FileUtils.getFile(outputDirectory, entry.getName());
206 if (outputFile.exists()) {
207 outputFile.delete();
208 }
209 File directory = outputFile.getParentFile();
210 directory.mkdirs();
211
212 try (FileOutputStream fos = new FileOutputStream(outputFile)) {
213 if (applyXmlExcludes(entry.getName()) && namespacePrefixes != null) {
214
215 try {
216 writeXmlWithExcludes(entry, entryStream, fos, namespacePrefixes);
217 }
218 catch (JDOMException ex) {
219 throw new PackageManagerException("Unable to parse XML file: " + entry.getName(), ex);
220 }
221 }
222 else {
223
224 IOUtils.copy(entryStream, fos);
225 }
226 }
227 }
228 }
229 }
230
231
232
233
234
235
236
237
238
239 private Set<String> getNamespacePrefixes(ZipFile zipFile, ZipArchiveEntry entry) throws IOException {
240 try (InputStream entryStream = zipFile.getInputStream(entry)) {
241 SAXParser parser = SAX_PARSER_FACTORY.newSAXParser();
242 final Set<String> prefixes = new LinkedHashSet<>();
243
244 final AtomicBoolean foundRootElement = new AtomicBoolean(false);
245 DefaultHandler handler = new DefaultHandler() {
246 @Override
247 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
248
249 if (StringUtils.equals(uri, JCR_NAMESPACE.getURI()) && StringUtils.equals(localName, "root")) {
250 foundRootElement.set(true);
251 }
252 }
253 @Override
254 public void startPrefixMapping(String prefix, String uri) throws SAXException {
255 if (StringUtils.isNotBlank(prefix)) {
256 prefixes.add(prefix);
257 }
258 }
259 };
260 parser.parse(entryStream, handler);
261
262 if (!foundRootElement.get()) {
263 return null;
264 }
265 else {
266 return prefixes;
267 }
268 }
269 catch (IOException | SAXException | ParserConfigurationException ex) {
270 throw new IOException("Error parsing " + entry.getName(), ex);
271 }
272 }
273
274 private void writeXmlWithExcludes(ZipArchiveEntry entry, InputStream inputStream, OutputStream outputStream, Set<String> namespacePrefixes)
275 throws IOException, JDOMException {
276 SAXBuilder saxBuilder = new SAXBuilder();
277 saxBuilder.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
278 saxBuilder.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
279 Document doc = saxBuilder.build(inputStream);
280
281 Set<String> namespacePrefixesActuallyUsed = new HashSet<>();
282
283
284 String namespacePrefix = getNamespacePrefix(entry.getName());
285 if (namespacePrefix != null) {
286 namespacePrefixesActuallyUsed.add(namespacePrefix);
287 }
288
289 applyXmlExcludes(doc.getRootElement(), getParentPath(entry), namespacePrefixesActuallyUsed, false);
290
291 XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()
292 .setIndent(" ")
293 .setLineSeparator(LineSeparator.UNIX));
294 outputter.setXMLOutputProcessor(new OneAttributePerLineXmlProcessor(namespacePrefixes, namespacePrefixesActuallyUsed));
295 outputter.output(doc, outputStream);
296 outputStream.flush();
297 }
298
299 static String getNamespacePrefix(String path) {
300 String fileName = FilenameUtils.getName(path);
301 if (StringUtils.equals(DOT_CONTENT_XML, fileName)) {
302 String parentFolderName = FilenameUtils.getName(FilenameUtils.getPathNoEndSeparator(path));
303 if (parentFolderName != null) {
304 String nodeName = PlatformNameFormat.getRepositoryName(parentFolderName);
305 Matcher matcher = FILENAME_NAMESPACE_PATTERN.matcher(nodeName);
306 if (matcher.matches()) {
307 return matcher.group(1);
308 }
309 }
310 }
311 return null;
312 }
313
314 private String getParentPath(ZipArchiveEntry entry) {
315 return StringUtils.removeEnd(StringUtils.removeStart(entry.getName(), ROOT_DIR), "/" + DOT_CONTENT_XML);
316 }
317
318 private String buildElementPath(Element element, String parentPath) {
319 StringBuilder path = new StringBuilder(parentPath);
320 if (!StringUtils.equals(element.getQualifiedName(), "jcr:root")) {
321 path.append("/").append(element.getQualifiedName());
322 }
323 return path.toString();
324 }
325
326 private void applyXmlExcludes(Element element, String parentPath, Set<String> namespacePrefixesActuallyUsed,
327 boolean insideReplicationElement) {
328 String path = buildElementPath(element, parentPath);
329 if (matches(path, this.excludeNodes, false)) {
330 element.detach();
331 return;
332 }
333 collectNamespacePrefix(namespacePrefixesActuallyUsed, element.getNamespacePrefix());
334
335 String jcrPrimaryType = element.getAttributeValue("primaryType", JCR_NAMESPACE);
336 boolean isRepositoryUserGroup = StringUtils.equals(jcrPrimaryType, "rep:User") || StringUtils.equals(jcrPrimaryType, "rep:Group");
337 boolean isReplicationElement = StringUtils.equals(jcrPrimaryType, "cq:Page")
338 || StringUtils.equals(jcrPrimaryType, "dam:Asset")
339 || StringUtils.equals(jcrPrimaryType, "cq:Template");
340 boolean isContent = insideReplicationElement && StringUtils.equals(element.getQualifiedName(), "jcr:content");
341 boolean setReplicationAttributes = isContent && markReplicationActivated;
342
343 List<Attribute> attributes = new ArrayList<>(element.getAttributes());
344 for (Attribute attribute : attributes) {
345 boolean excluded = false;
346 if (matches(attribute.getQualifiedName(), this.excludeProperties, false)) {
347 if (isRepositoryUserGroup && StringUtils.equals(attribute.getQualifiedName(), JcrConstants.JCR_UUID)) {
348
349 }
350 else {
351 attribute.detach();
352 excluded = true;
353 }
354 }
355 else if (StringUtils.equals(attribute.getQualifiedName(), PRIMARYTYPE_PROPERTY)) {
356 String namespacePrefix = StringUtils.substringBefore(attribute.getValue(), ":");
357 collectNamespacePrefix(namespacePrefixesActuallyUsed, namespacePrefix);
358 }
359 else if (StringUtils.equals(attribute.getQualifiedName(), MIXINS_PROPERTY)) {
360 String filteredValue = filterMixinsPropertyValue(attribute.getValue(), namespacePrefixesActuallyUsed);
361 if (StringUtils.isBlank(filteredValue)) {
362 attribute.detach();
363 }
364 else {
365 attribute.setValue(filteredValue);
366 }
367 }
368 else if (StringUtils.startsWith(attribute.getValue(), "{Name}")) {
369 collectNamespacePrefixNameArray(namespacePrefixesActuallyUsed, attribute.getQualifiedName(), attribute.getValue());
370
371 attribute.setValue(sortReferenceValues(attribute.getQualifiedName(), attribute.getValue(), PropertyType.NAME));
372 }
373 else if (StringUtils.startsWith(attribute.getValue(), "{WeakReference}")) {
374
375 attribute.setValue(sortReferenceValues(attribute.getQualifiedName(), attribute.getValue(), PropertyType.WEAKREFERENCE));
376 }
377 if (!excluded) {
378 collectNamespacePrefix(namespacePrefixesActuallyUsed, attribute.getNamespacePrefix());
379 }
380 }
381
382
383 if (setReplicationAttributes && matches(path, markReplicationActivatedIncludeNodes, true)) {
384 addMixin(element, "cq:ReplicationStatus");
385 element.setAttribute("lastReplicated", "{Date}" + dateLastReplicated, CQ_NAMESPACE);
386 element.setAttribute("lastReplicationAction", "Activate", CQ_NAMESPACE);
387 collectNamespacePrefix(namespacePrefixesActuallyUsed, CQ_NAMESPACE.getPrefix());
388 }
389
390 List<Element> children = new ArrayList<>(element.getChildren());
391 for (Element child : children) {
392 applyXmlExcludes(child, path, namespacePrefixesActuallyUsed, (insideReplicationElement || isReplicationElement) && !isContent);
393 }
394 }
395
396 private String filterMixinsPropertyValue(String value, Set<String> namespacePrefixesActuallyUsed) {
397 if (this.excludeMixins.length == 0 || StringUtils.isBlank(value)) {
398 return value;
399 }
400
401 DocViewProperty prop = DocViewProperty.parse(MIXINS_PROPERTY, value);
402 List<Value> mixins = new ArrayList<>();
403 for (int i = 0; i < prop.values.length; i++) {
404 String mixin = prop.values[i];
405 if (!matches(mixin, this.excludeMixins, false)) {
406 String namespacePrefix = StringUtils.substringBefore(mixin, ":");
407 collectNamespacePrefix(namespacePrefixesActuallyUsed, namespacePrefix);
408 mixins.add(new MockValue(mixin, PropertyType.STRING));
409 }
410 }
411
412 if (mixins.isEmpty()) {
413 return null;
414 }
415
416 try {
417 return DocViewProperty.format(new MockProperty(MIXINS_PROPERTY, true, mixins.toArray(new Value[0])));
418 }
419 catch (RepositoryException ex) {
420 throw new RuntimeException("Unable to format value for " + MIXINS_PROPERTY, ex);
421 }
422 }
423
424 private void addMixin(Element element, String mixin) {
425 String mixinsString = element.getAttributeValue("mixinTypes", JCR_NAMESPACE);
426
427 List<String> mixins = new ArrayList<>();
428 if (!StringUtils.isBlank(mixinsString)) {
429 DocViewProperty prop = DocViewProperty.parse(MIXINS_PROPERTY, mixinsString);
430 for (int i = 0; i < prop.values.length; i++) {
431 mixins.add(prop.values[i]);
432 }
433 }
434 if (!mixins.contains(mixin)) {
435 mixins.add(mixin);
436 }
437
438 try {
439 Value[] values = mixins.stream()
440 .map(item -> new MockValue(item, PropertyType.STRING))
441 .toArray(size -> new Value[size]);
442 mixinsString = DocViewProperty.format(new MockProperty(MIXINS_PROPERTY, true, values));
443 element.setAttribute("mixinTypes", mixinsString, JCR_NAMESPACE);
444 }
445 catch (RepositoryException ex) {
446 throw new RuntimeException("Unable to format value for " + MIXINS_PROPERTY, ex);
447 }
448 }
449
450 private void collectNamespacePrefix(Set<String> prefixes, String prefix) {
451 if (StringUtils.isNotBlank(prefix)) {
452 prefixes.add(prefix);
453 }
454 }
455
456 private void collectNamespacePrefixNameArray(Set<String> prefixes, String name, String value) {
457 DocViewProperty prop = DocViewProperty.parse(name, value);
458 for (int i = 0; i < prop.values.length; i++) {
459 String item = prop.values[i];
460 String namespacePrefix = StringUtils.substringBefore(item, ":");
461 collectNamespacePrefix(prefixes, namespacePrefix);
462 }
463 }
464
465
466
467
468
469
470
471
472 private String sortReferenceValues(String name, String value, int propertyType) {
473 Set<String> refs = new TreeSet<>();
474 DocViewProperty prop = DocViewProperty.parse(name, value);
475 for (int i = 0; i < prop.values.length; i++) {
476 refs.add(prop.values[i]);
477 }
478 List<Value> values = new ArrayList<>();
479 for (String ref : refs) {
480 values.add(new MockValue(ref, propertyType));
481 }
482 try {
483 return DocViewProperty.format(new MockProperty(name, true, values.toArray(new Value[0])));
484 }
485 catch (RepositoryException ex) {
486 throw new RuntimeException("Unable to format value for " + name, ex);
487 }
488 }
489
490
491
492
493
494 private static class MockProperty implements Property, PropertyDefinition {
495
496 private final String name;
497 private final boolean multiple;
498 private final Value[] values;
499
500 MockProperty(String name, boolean multiple, Value[] values) {
501 this.name = name;
502 this.multiple = multiple;
503 this.values = values;
504 }
505
506 @Override
507 public String getName() {
508 return name;
509 }
510
511 @Override
512 public int getType() {
513 if (values.length > 0) {
514 return values[0].getType();
515 }
516 return PropertyType.UNDEFINED;
517 }
518
519 @Override
520 public boolean isMultiple() {
521 return multiple;
522 }
523
524 @Override
525 public Value getValue() throws ValueFormatException {
526 if (multiple) {
527 throw new ValueFormatException("Property is multiple.");
528 }
529 return values[0];
530 }
531
532 @Override
533 public Value[] getValues() throws ValueFormatException {
534 if (!multiple) {
535 throw new ValueFormatException("Property is not multiple.");
536 }
537 return values;
538 }
539
540 @Override
541 public PropertyDefinition getDefinition() {
542 return this;
543 }
544
545
546
547
548 @Override
549 public String getPath() {
550 throw new UnsupportedOperationException();
551 }
552
553 @Override
554 public Item getAncestor(int depth) {
555 throw new UnsupportedOperationException();
556 }
557
558 @Override
559 public Node getParent() {
560 throw new UnsupportedOperationException();
561 }
562
563 @Override
564 public int getDepth() {
565 throw new UnsupportedOperationException();
566 }
567
568 @Override
569 public Session getSession() {
570 throw new UnsupportedOperationException();
571 }
572
573 @Override
574 public boolean isNode() {
575 throw new UnsupportedOperationException();
576 }
577
578 @Override
579 public boolean isNew() {
580 throw new UnsupportedOperationException();
581 }
582
583 @Override
584 public boolean isModified() {
585 throw new UnsupportedOperationException();
586 }
587
588 @Override
589 public boolean isSame(Item otherItem) {
590 throw new UnsupportedOperationException();
591 }
592
593 @Override
594 public void accept(ItemVisitor visitor) {
595 throw new UnsupportedOperationException();
596 }
597
598 @Override
599 public void save() {
600 throw new UnsupportedOperationException();
601 }
602
603 @Override
604 public void refresh(boolean keepChanges) {
605 throw new UnsupportedOperationException();
606 }
607
608 @Override
609 public void remove() {
610 throw new UnsupportedOperationException();
611 }
612
613 @Override
614 public void setValue(Value value) {
615 throw new UnsupportedOperationException();
616 }
617
618 @Override
619 public void setValue(Value[] value) {
620 throw new UnsupportedOperationException();
621 }
622
623 @Override
624 public void setValue(String value) {
625 throw new UnsupportedOperationException();
626 }
627
628 @Override
629 public void setValue(String[] value) {
630 throw new UnsupportedOperationException();
631 }
632
633 @Override
634 public void setValue(InputStream value) {
635 throw new UnsupportedOperationException();
636 }
637
638 @Override
639 public void setValue(Binary value) {
640 throw new UnsupportedOperationException();
641 }
642
643 @Override
644 public void setValue(long value) {
645 throw new UnsupportedOperationException();
646 }
647
648 @Override
649 public void setValue(double value) {
650 throw new UnsupportedOperationException();
651 }
652
653 @Override
654 public void setValue(BigDecimal value) {
655 throw new UnsupportedOperationException();
656 }
657
658 @Override
659 public void setValue(Calendar value) {
660 throw new UnsupportedOperationException();
661 }
662
663 @Override
664 public void setValue(boolean value) {
665 throw new UnsupportedOperationException();
666 }
667
668 @Override
669 public void setValue(Node value) {
670 throw new UnsupportedOperationException();
671 }
672
673 @Override
674 public String getString() {
675 throw new UnsupportedOperationException();
676 }
677
678 @Override
679 public InputStream getStream() {
680 throw new UnsupportedOperationException();
681 }
682
683 @Override
684 public Binary getBinary() {
685 throw new UnsupportedOperationException();
686 }
687
688 @Override
689 public long getLong() {
690 throw new UnsupportedOperationException();
691 }
692
693 @Override
694 public double getDouble() {
695 throw new UnsupportedOperationException();
696 }
697
698 @Override
699 public BigDecimal getDecimal() {
700 throw new UnsupportedOperationException();
701 }
702
703 @Override
704 public Calendar getDate() {
705 throw new UnsupportedOperationException();
706 }
707
708 @Override
709 public boolean getBoolean() {
710 throw new UnsupportedOperationException();
711 }
712
713 @Override
714 public Node getNode() {
715 throw new UnsupportedOperationException();
716 }
717
718 @Override
719 public Property getProperty() {
720 throw new UnsupportedOperationException();
721 }
722
723 @Override
724 public long getLength() {
725 throw new UnsupportedOperationException();
726 }
727
728 @Override
729 public long[] getLengths() {
730 throw new UnsupportedOperationException();
731 }
732
733 @Override
734 public NodeType getDeclaringNodeType() {
735 throw new UnsupportedOperationException();
736 }
737
738 @Override
739 public boolean isAutoCreated() {
740 throw new UnsupportedOperationException();
741 }
742
743 @Override
744 public boolean isMandatory() {
745 throw new UnsupportedOperationException();
746 }
747
748 @Override
749 public int getOnParentVersion() {
750 throw new UnsupportedOperationException();
751 }
752
753 @Override
754 public boolean isProtected() {
755 throw new UnsupportedOperationException();
756 }
757
758 @Override
759 public int getRequiredType() {
760 throw new UnsupportedOperationException();
761 }
762
763 @Override
764 public String[] getValueConstraints() {
765 throw new UnsupportedOperationException();
766 }
767
768 @Override
769 public Value[] getDefaultValues() {
770 throw new UnsupportedOperationException();
771 }
772
773 @Override
774 public String[] getAvailableQueryOperators() {
775 throw new UnsupportedOperationException();
776 }
777
778 @Override
779 public boolean isFullTextSearchable() {
780 throw new UnsupportedOperationException();
781 }
782
783 @Override
784 public boolean isQueryOrderable() {
785 throw new UnsupportedOperationException();
786 }
787
788 }
789
790 private static class MockValue implements Value {
791
792 private final String value;
793 private final int type;
794
795 MockValue(String value, int type) {
796 this.value = value;
797 this.type = type;
798 }
799
800 @Override
801 public String getString() {
802 return value;
803 }
804
805 @Override
806 public int getType() {
807 return type;
808 }
809
810
811
812
813 @Override
814 public InputStream getStream() {
815 throw new UnsupportedOperationException();
816 }
817
818 @Override
819 public Binary getBinary() {
820 throw new UnsupportedOperationException();
821 }
822
823 @Override
824 public long getLong() {
825 throw new UnsupportedOperationException();
826 }
827
828 @Override
829 public double getDouble() {
830 throw new UnsupportedOperationException();
831 }
832
833 @Override
834 public BigDecimal getDecimal() {
835 throw new UnsupportedOperationException();
836 }
837
838 @Override
839 public Calendar getDate() {
840 throw new UnsupportedOperationException();
841 }
842
843 @Override
844 public boolean getBoolean() {
845 throw new UnsupportedOperationException();
846 }
847
848 }
849
850 }