001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.lenya.cms.repository;
019:
020: import java.io.ByteArrayInputStream;
021: import java.io.ByteArrayOutputStream;
022: import java.io.File;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.io.OutputStream;
026: import java.net.MalformedURLException;
027: import java.util.Date;
028: import java.util.Map;
029: import java.util.WeakHashMap;
030:
031: import org.apache.avalon.framework.logger.AbstractLogEnabled;
032: import org.apache.avalon.framework.logger.Logger;
033: import org.apache.avalon.framework.service.ServiceException;
034: import org.apache.avalon.framework.service.ServiceManager;
035: import org.apache.excalibur.source.ModifiableSource;
036: import org.apache.excalibur.source.SourceResolver;
037: import org.apache.excalibur.source.TraversableSource;
038: import org.apache.lenya.cms.cocoon.source.SourceUtil;
039: import org.apache.lenya.cms.publication.DocumentFactory;
040: import org.apache.lenya.cms.publication.DocumentUtil;
041: import org.apache.lenya.cms.publication.Publication;
042: import org.apache.lenya.cms.rc.CheckInEntry;
043: import org.apache.lenya.cms.rc.RevisionControlException;
044: import org.apache.lenya.util.Assert;
045:
046: /**
047: * Provide access to a source.
048: */
049: public class SourceWrapper extends AbstractLogEnabled {
050:
051: private SourceNode node;
052: private String sourceUri;
053: protected ServiceManager manager;
054:
055: /**
056: * Ctor.
057: * @param node
058: * @param sourceUri
059: * @param manager
060: * @param logger
061: */
062: public SourceWrapper(SourceNode node, String sourceUri,
063: ServiceManager manager, Logger logger) {
064:
065: Assert.notNull("node", node);
066: this .node = node;
067:
068: Assert.notNull("source URI", sourceUri);
069: this .sourceUri = sourceUri;
070:
071: Assert.notNull("service manager", manager);
072: this .manager = manager;
073:
074: enableLogging(logger);
075: }
076:
077: protected static final String FILE_PREFIX = "file:/";
078: protected static final String CONTEXT_PREFIX = "context://";
079:
080: protected SourceNode getNode() {
081: return this .node;
082: }
083:
084: private String realSourceUri;
085:
086: /**
087: * Returns the URI of the actual source which is used.
088: *
089: * @return A string.
090: */
091: protected String getRealSourceUri() {
092: if (this .realSourceUri == null) {
093: this .realSourceUri = computeRealSourceUri(this .manager,
094: getNode().getSession(), this .sourceUri, getLogger());
095: }
096: return this .realSourceUri;
097: }
098:
099: protected static final String computeRealSourceUri(
100: ServiceManager manager, Session session, String sourceUri,
101: Logger logger) {
102: String contentDir = null;
103: String publicationId = null;
104: try {
105: String pubBase = Node.LENYA_PROTOCOL
106: + Publication.PUBLICATION_PREFIX_URI + "/";
107: String publicationsPath = sourceUri.substring(pubBase
108: .length());
109: int firstSlashIndex = publicationsPath.indexOf("/");
110: publicationId = publicationsPath.substring(0,
111: firstSlashIndex);
112: DocumentFactory factory = DocumentUtil
113: .createDocumentFactory(manager, session);
114: Publication pub = factory.getPublication(publicationId);
115: contentDir = pub.getContentDir();
116: } catch (Exception e) {
117: throw new RuntimeException(e);
118: }
119:
120: String contentBaseUri = null;
121: String urlID = sourceUri
122: .substring(Node.LENYA_PROTOCOL.length());
123:
124: // Substitute e.g. "lenya://lenya/pubs/PUB_ID/content" by "contentDir"
125: String filePrefix = urlID.substring(0, urlID
126: .indexOf(publicationId))
127: + publicationId;
128: String tempString = urlID.substring(filePrefix.length() + 1);
129: String fileMiddle = tempString.substring(0, tempString
130: .indexOf("/"));
131: String fileSuffix = tempString.substring(
132: fileMiddle.length() + 1, tempString.length());
133: String uriSuffix;
134: if (new File(contentDir).isAbsolute()) {
135: // Absolute
136: contentBaseUri = FILE_PREFIX + contentDir;
137: uriSuffix = File.separator + fileSuffix;
138: } else {
139: // Relative
140: contentBaseUri = CONTEXT_PREFIX + contentDir;
141: uriSuffix = "/" + fileSuffix;
142: }
143:
144: String realSourceUri = contentBaseUri + uriSuffix;
145:
146: if (logger.isDebugEnabled()) {
147: try {
148: if (!SourceUtil.exists(contentBaseUri, manager)) {
149: logger
150: .debug("The content directory ["
151: + contentBaseUri
152: + "] does not exist. "
153: + "It will be created as soon as documents are added.");
154: }
155: } catch (ServiceException e) {
156: throw new RuntimeException(e);
157: } catch (MalformedURLException e) {
158: throw new RuntimeException(e);
159: } catch (IOException e) {
160: throw new RuntimeException(e);
161: }
162: logger.debug("Real Source URI: " + realSourceUri);
163: }
164:
165: return realSourceUri;
166: }
167:
168: /**
169: * @throws RepositoryException if an error occurs.
170: * @see org.apache.lenya.transaction.Transactionable#deleteTransactionable()
171: */
172: public void deleteTransactionable() throws RepositoryException {
173: try {
174: if (!getNode().isCheckedOut()) {
175: throw new RuntimeException("Cannot delete source ["
176: + getSourceUri() + "]: not checked out!");
177: } else {
178: this .data = null;
179: SourceUtil.delete(getRealSourceUri(), this .manager);
180: }
181: } catch (Exception e) {
182: throw new RepositoryException(e);
183: }
184: }
185:
186: byte[] data = null;
187:
188: /**
189: * @return An input stream.
190: * @throws RepositoryException if an error occurs.
191: * @see org.apache.lenya.cms.repository.Node#getInputStream()
192: */
193: public synchronized InputStream getInputStream()
194: throws RepositoryException {
195: loadData();
196: if (this .data == null) {
197: throw new RuntimeException(this + " does not exist!");
198: }
199: return new ByteArrayInputStream(this .data);
200: }
201:
202: /**
203: * @return A boolean value.
204: * @throws RepositoryException if an error occurs.
205: * @see org.apache.lenya.cms.repository.Node#exists()
206: */
207: public boolean exists() throws RepositoryException {
208: if (this .deleted == true) {
209: return false;
210: } else if (this .data != null) {
211: return true;
212: } else {
213: try {
214: return SourceUtil.exists(getRealSourceUri(),
215: this .manager);
216: } catch (Exception e) {
217: throw new RepositoryException(e);
218: }
219: }
220: }
221:
222: private boolean deleted;
223: private int loadRevision = -1;
224:
225: protected void delete() {
226: this .deleted = true;
227: }
228:
229: /**
230: * Loads the data from the real source.
231: *
232: * @throws RepositoryException if an error occurs.
233: */
234: protected synchronized void loadData() throws RepositoryException {
235:
236: if (this .deleted || this .data != null) {
237: return;
238: }
239:
240: ByteArrayOutputStream out = null;
241: InputStream in = null;
242: SourceResolver resolver = null;
243: TraversableSource source = null;
244: try {
245: resolver = (SourceResolver) this .manager
246: .lookup(SourceResolver.ROLE);
247: source = (TraversableSource) resolver
248: .resolveURI(getRealSourceUri());
249:
250: if (source.exists() && !source.isCollection()) {
251: byte[] buf = new byte[4096];
252: out = new ByteArrayOutputStream();
253: in = source.getInputStream();
254: int read = in.read(buf);
255:
256: while (read > 0) {
257: out.write(buf, 0, read);
258: read = in.read(buf);
259: }
260:
261: this .data = out.toByteArray();
262: this .mimeType = source.getMimeType();
263: }
264: } catch (Exception e) {
265: throw new RepositoryException(e);
266: } finally {
267: try {
268: if (in != null)
269: in.close();
270: if (out != null)
271: out.close();
272: } catch (Exception e) {
273: throw new RepositoryException(e);
274: }
275:
276: if (resolver != null) {
277: if (source != null) {
278: resolver.release(source);
279: }
280: this .manager.release(resolver);
281: }
282: }
283: this .loadRevision = this .node.getCurrentRevisionNumber();
284: }
285:
286: /**
287: * Store the source URLs which are currently written.
288: */
289: private static Map lockedUris = new WeakHashMap();
290:
291: /**
292: * @throws RepositoryException if an error occurs.
293: * @see org.apache.lenya.transaction.Transactionable#saveTransactionable()
294: */
295: protected synchronized void saveTransactionable()
296: throws RepositoryException {
297: if (getLogger().isDebugEnabled()) {
298: getLogger().debug(
299: "Saving [" + this + "] to source ["
300: + getRealSourceUri() + "]");
301: }
302:
303: if (this .data != null) {
304:
305: String realSourceUri = getRealSourceUri();
306: Object lock = lockedUris.get(realSourceUri);
307: if (lock == null) {
308: lock = new Object();
309: lockedUris.put(realSourceUri, lock);
310: }
311:
312: synchronized (lock) {
313: saveTransactionable(realSourceUri);
314: }
315: }
316: }
317:
318: protected void saveTransactionable(String realSourceUri)
319: throws RepositoryException {
320: SourceResolver resolver = null;
321: ModifiableSource source = null;
322: InputStream in = null;
323: OutputStream out = null;
324:
325: try {
326: resolver = (SourceResolver) manager
327: .lookup(SourceResolver.ROLE);
328: source = (ModifiableSource) resolver
329: .resolveURI(realSourceUri);
330:
331: out = source.getOutputStream();
332:
333: byte[] buf = new byte[4096];
334: in = new ByteArrayInputStream(this .data);
335: int read = in.read(buf);
336:
337: while (read > 0) {
338: out.write(buf, 0, read);
339: read = in.read(buf);
340: }
341: } catch (Exception e) {
342: throw new RepositoryException(e);
343: } finally {
344:
345: try {
346: if (in != null) {
347: in.close();
348: }
349: if (out != null) {
350: out.flush();
351: out.close();
352: }
353: } catch (Throwable t) {
354: throw new RuntimeException("Could not close streams: ",
355: t);
356: }
357:
358: if (resolver != null) {
359: if (source != null) {
360: resolver.release(source);
361: }
362: manager.release(resolver);
363: }
364: }
365: }
366:
367: /**
368: * Output stream.
369: */
370: private class NodeOutputStream extends ByteArrayOutputStream {
371: /**
372: * @see java.io.OutputStream#close()
373: */
374: public synchronized void close() throws IOException {
375: SourceWrapper.this .data = super .toByteArray();
376: SourceWrapper.this .lastModified = new Date().getTime();
377: try {
378: SourceWrapper.this .getNode().registerDirty();
379: } catch (RepositoryException e) {
380: throw new RuntimeException(e);
381: }
382: super .close();
383: }
384: }
385:
386: /**
387: * @return The content length.
388: * @throws RepositoryException if an error occurs.
389: * @see org.apache.lenya.cms.repository.Node#getContentLength()
390: */
391: public long getContentLength() throws RepositoryException {
392: loadData();
393: return this .data.length;
394: }
395:
396: private long lastModified = -1;
397:
398: /**
399: * @return The last modification date.
400: * @throws RepositoryException if an error occurs.
401: * @see org.apache.lenya.cms.repository.Node#getLastModified()
402: */
403: public long getLastModified() throws RepositoryException {
404: try {
405: CheckInEntry entry = this .node.getRcml()
406: .getLatestCheckInEntry();
407: if (entry != null) {
408: this .lastModified = entry.getTime();
409: }
410: } catch (RevisionControlException e) {
411: throw new RepositoryException(e);
412: }
413: return this .lastModified;
414: }
415:
416: private String mimeType;
417:
418: /**
419: * @return A string.
420: * @throws RepositoryException if an error occurs.
421: * @see org.apache.lenya.cms.repository.Node#getMimeType()
422: */
423: public String getMimeType() throws RepositoryException {
424: loadData();
425: return this .mimeType;
426: }
427:
428: /**
429: * @return The source URI.
430: */
431: public String getSourceUri() {
432: return this .sourceUri;
433: }
434:
435: /**
436: * @return An output stream.
437: * @throws RepositoryException if an error occurs.
438: * @see org.apache.lenya.cms.repository.Node#getOutputStream()
439: */
440: public synchronized OutputStream getOutputStream()
441: throws RepositoryException {
442: if (getLogger().isDebugEnabled())
443: getLogger().debug("Get OutputStream for " + getSourceUri());
444: loadData();
445: return new NodeOutputStream();
446: }
447:
448: protected int getLoadRevision() {
449: return this.loadRevision;
450: }
451:
452: }
|