source: OAIProvider/trunk/src/main/java/eu/clarin/oai/provider/impl/OAIOutputStreamImpl.java @ 1914

Last change on this file since 1914 was 1914, checked in by oschonef, 12 years ago
  • more refactoring
  • Property svn:eol-style set to native
File size: 10.9 KB
Line 
1package eu.clarin.oai.provider.impl;
2
3import java.io.FilterOutputStream;
4import java.io.IOException;
5import java.io.OutputStream;
6import java.util.Date;
7import java.util.List;
8import java.util.Map;
9import java.util.TimeZone;
10
11import javax.xml.XMLConstants;
12import javax.xml.stream.XMLOutputFactory;
13import javax.xml.stream.XMLStreamException;
14import javax.xml.stream.XMLStreamWriter;
15
16import org.apache.commons.lang.time.FastDateFormat;
17
18import eu.clarin.oai.provider.MetadataFormat;
19import eu.clarin.oai.provider.OAIException;
20import eu.clarin.oai.provider.Record;
21import eu.clarin.oai.provider.Repository;
22import eu.clarin.oai.provider.ext.OAIOutputStream;
23import eu.clarin.oai.provider.ext.RepositoryAdapter;
24import eu.clarin.oai.provider.ext.ResumptionToken;
25import eu.clarin.oai.provider.ext.VerbContext;
26
27final class OAIOutputStreamImpl implements OAIOutputStream {
28    private final static class FlushSkipOutputStream
29            extends FilterOutputStream {
30        private byte[] buf;
31        private int bufCount = 0;
32
33        public FlushSkipOutputStream(OutputStream out, int bufsize) {
34            super(out);
35            this.buf = new byte[(bufsize > 1024 ? bufsize : 1024)];
36        }
37
38        @Override
39        public synchronized void write(byte[] buffer, int offset, int length)
40                throws IOException {
41            if (buf == null) {
42                throw new IOException("stream already closed");
43            }
44            if (buffer == null) {
45                throw new NullPointerException("buffer == null");
46            }
47            if (offset < 0 || offset > buffer.length - length) {
48                throw new ArrayIndexOutOfBoundsException(
49                        "offset out of bounds: " + offset);
50            }
51            if (length < 0) {
52                throw new ArrayIndexOutOfBoundsException(
53                        "length out of bounds: " + length);
54            }
55            ensureCapacity(length);
56            System.arraycopy(buffer, offset, buf, bufCount, length);
57            bufCount += length;
58        }
59
60        @Override
61        public synchronized void write(int b) throws IOException {
62            if (buf == null) {
63                throw new IOException("stream already closed");
64            }
65            ensureCapacity(1);
66            buf[bufCount++] = (byte) (b & 0xFF);
67        }
68
69        @Override
70        public synchronized void close() throws IOException {
71            if (buf == null) {
72                return;
73            }
74            try {
75                ensureCapacity(buf.length); // explicitly force flush
76                super.close();
77            } finally {
78                buf = null;
79            }
80        }
81
82        @Override
83        public synchronized void flush() throws IOException {
84            // do nothing, defer flush() as long as possible
85        }
86
87        private void ensureCapacity(int needed) throws IOException {
88            if (needed >= (buf.length - bufCount)) {
89                out.write(buf, 0, bufCount);
90                bufCount = 0;
91            }
92        }
93    } // inner class FlushSkipOutputStream
94    private static final String NS_OAI =
95        "http://www.openarchives.org/OAI/2.0/";
96    private static final String NS_OAI_SCHEMA_LOCATION =
97        "http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd";
98    private static final String SCHEMA_LOCATION =
99        NS_OAI + " " + NS_OAI_SCHEMA_LOCATION;
100    private static final XMLOutputFactory writerFactory =
101        XMLOutputFactory.newInstance();
102    private final RepositoryAdapter repository;
103    private final OutputStream stream;
104    private final XMLStreamWriter writer;
105
106    OAIOutputStreamImpl(VerbContext ctx, OutputStream out)
107            throws XMLStreamException {
108        this.repository = ctx.getRepository();
109        this.stream = new FlushSkipOutputStream(out, 8192);
110        writer = writerFactory.createXMLStreamWriter(stream, "utf-8");
111        writer.writeStartDocument("utf-8", "1.0");
112
113        // FIXME: make this a configuration option
114        StringBuilder data = new StringBuilder();
115        data.append("type=\"text/xsl\" href=\"");
116        data.append(ctx.getContextPath());
117        data.append("/oai2.xsl\"");
118        writer.writeProcessingInstruction("xml-stylesheet", data.toString());
119
120        writer.setDefaultNamespace(NS_OAI);
121        writer.writeStartElement("OAI-PMH");
122        writer.writeDefaultNamespace(NS_OAI);
123        writer.writeNamespace("xsi",
124                XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
125        writer.writeAttribute(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
126                "schemaLocation", SCHEMA_LOCATION);
127
128        FastDateFormat fmt = getDateFormat(Repository.Granularity.SECONDS);
129        writer.writeStartElement("responseDate");
130        writer.writeCharacters(fmt.format(System.currentTimeMillis()));
131        writer.writeEndElement(); // responseDate element
132
133        writer.writeStartElement("request");
134        if (ctx.getVerb() != null) {
135            writer.writeAttribute("verb", ctx.getVerb());
136            Map<String, String> args = ctx.getUnparsedArguments();
137            for (Map.Entry<String, String> item : args.entrySet()) {
138                writer.writeAttribute(item.getKey(), item.getValue());
139            }
140        }
141        writer.writeCharacters(ctx.getRequestURI());
142        writer.writeEndElement(); // request element
143    }
144
145    @Override
146    public void close() throws IOException, XMLStreamException {
147        writer.writeEndElement(); // OAI-PMH (root) element
148        writer.writeEndDocument();
149        writer.flush();
150        writer.close();
151        // explicitly close output stream, as XMLStreamWriter does not!
152        stream.close();
153    }
154
155    @Override
156    public void flush() throws XMLStreamException {
157        writer.flush();
158    }
159
160    @Override
161    public XMLStreamWriter getXMLStreamWriter() throws XMLStreamException {
162        return writer;
163    }
164
165    @Override
166    public void writeStartElement(String localName) throws XMLStreamException {
167        writer.writeStartElement(localName);
168    }
169
170    @Override
171    public void writeStartElement(String namespaceURI, String localName)
172            throws XMLStreamException {
173        writer.writeStartElement(namespaceURI, localName);
174    }
175
176    @Override
177    public void writeStartElement(String namespaceURI, String localName,
178            List<NamespaceDecl> decls) throws XMLStreamException {
179        for (NamespaceDecl decl : decls) {
180            writer.setPrefix(decl.getPrefix(), decl.getNamespaceURI());
181        }
182        writer.writeStartElement(namespaceURI, localName);
183        boolean schemaDeclWritten = false;
184        for (NamespaceDecl decl : decls) {
185            writer.writeNamespace(decl.getPrefix(), decl.getNamespaceURI());
186            if (decl.hasSchemaLocation()) {
187                /*
188                 * From an XML point of view, the XSI-namespace is still in
189                 * scope and this is not needed, but all other providers show
190                 * this behavior.
191                 */
192                if (!schemaDeclWritten) {
193                    writer.writeNamespace("xsi",
194                            XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
195                    schemaDeclWritten = true;
196                }
197                writer.writeAttribute(
198                        XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
199                        "schemaLocation",
200                        decl.getNamespaceURI() + " " + decl.getSchemaLocation());
201            }
202        }
203    }
204
205    @Override
206    public void writeEndElement() throws XMLStreamException {
207        writer.writeEndElement();
208    }
209
210    @Override
211    public void writeAttribute(String localName, String value)
212            throws XMLStreamException {
213        writer.writeAttribute(localName, value);
214    }
215
216    @Override
217    public void writeAttribute(String namespaceURI, String localName,
218            String value) throws XMLStreamException {
219        writer.writeAttribute(namespaceURI, localName, value);
220    }
221
222    @Override
223    public void writeCharacters(String text) throws XMLStreamException {
224        writer.writeCharacters(text);
225    }
226
227    @Override
228    public void writeDate(Date date) throws XMLStreamException {
229        FastDateFormat fmt = getDateFormat(repository.getGranularity());
230        writer.writeCharacters(fmt.format(date));
231    }
232
233    @Override
234    public void writeRecordHeader(Record record) throws XMLStreamException {
235        writer.writeStartElement("header");
236        if (record.isDeleted()) {
237            writer.writeAttribute("status", "deleted");
238        }
239        writer.writeStartElement("identifier");
240        writer.writeCharacters(repository.createRecordId(record.getLocalId()));
241        writer.writeEndElement(); // identifier element
242        writer.writeStartElement("datestamp");
243        writeDate(record.getDatestamp());
244        writer.writeEndElement(); // datestamp element
245        List<String> setSpecs = record.getSetSpecs();
246        if (setSpecs != null) {
247            for (String setSpec : setSpecs) {
248                writer.writeStartElement("setSpec");
249                writer.writeCharacters(setSpec);
250                writer.writeEndElement(); // setSpec element
251            }
252        }
253        writer.writeEndElement(); // header element
254    }
255
256    @Override
257    public void writeRecord(Record record, MetadataFormat format)
258            throws XMLStreamException, OAIException {
259        writer.writeStartElement("record");
260        writeRecordHeader(record);
261        if (!record.isDeleted()) {
262            writer.writeStartElement("metadata");
263            // FIXME: re-work!
264            record.writeRecord(writer, format);
265            writer.writeEndElement(); // metadata element
266        }
267        writer.writeEndElement(); // record element
268    }
269
270    @Override
271    public void writeResumptionToken(ResumptionToken token)
272            throws XMLStreamException {
273        writer.writeStartElement("resumptionToken");
274        if (token != null) {
275            FastDateFormat fmt = getDateFormat(Repository.Granularity.SECONDS);
276            writer.writeAttribute("expirationDate",
277                    fmt.format(token.getExpirationDate()));
278            if (token.getCursor() >= 0) {
279                writer.writeAttribute("cursor",
280                        Integer.toString(token.getCursor()));
281            }
282            if (token.getCompleteListSize() > 0) {
283                writer.writeAttribute("completeListSize",
284                        Integer.toString(token.getCompleteListSize()));
285            }
286            writer.writeCharacters(Long.toString(token.getId()));
287        }
288        writer.writeEndElement(); // resumptionToken element
289    }
290
291    private FastDateFormat getDateFormat(Repository.Granularity g) {
292        switch (g) {
293        case DAYS:
294            return FastDateFormat.getInstance("yyyy-MM-dd",
295                                              TimeZone.getTimeZone("UTC"));
296        default:
297            return FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",
298                                              TimeZone.getTimeZone("UTC"));
299        } // switch
300    }
301
302} // class OAIOutputStreamImpl
Note: See TracBrowser for help on using the repository browser.