001: /*
002: * Copyright 2001-2007 Hippo (www.hippo.nl)
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package nl.hippo.tree.generation;
017:
018: import java.io.IOException;
019: import java.util.Date;
020: import java.util.Enumeration;
021: import java.util.HashMap;
022: import java.util.HashSet;
023: import java.util.Iterator;
024: import java.util.Map;
025: import java.util.Vector;
026: import org.apache.avalon.framework.context.Context;
027: import org.apache.avalon.framework.context.ContextException;
028: import org.apache.avalon.framework.context.Contextualizable;
029: import org.apache.avalon.framework.parameters.Parameters;
030: import org.apache.avalon.framework.service.ServiceException;
031: import org.apache.avalon.framework.service.ServiceManager;
032: import org.apache.avalon.framework.service.Serviceable;
033: import org.apache.cocoon.ProcessingException;
034: import org.apache.cocoon.components.ContextHelper;
035: import org.apache.cocoon.environment.Request;
036: import org.apache.cocoon.environment.Session;
037: import org.apache.cocoon.environment.SourceResolver;
038: import org.apache.cocoon.generation.AbstractGenerator;
039: import org.apache.commons.httpclient.Header;
040: import org.apache.commons.httpclient.HttpClient;
041: import org.apache.commons.httpclient.HttpState;
042: import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
043: import org.apache.webdav.lib.Property;
044: import org.apache.webdav.lib.methods.PropFindMethod;
045: import org.xml.sax.SAXException;
046: import org.xml.sax.helpers.AttributesImpl;
047:
048: import org.apache.excalibur.store.Store;
049: import org.apache.cocoon.components.store.impl.EventAwareTransientStore;
050: import org.apache.cocoon.caching.validity.NamedEvent;
051:
052: public class TreeGenerator extends AbstractGenerator implements
053: Contextualizable, Serviceable {
054: // connection state
055: private HttpState httpState = null;
056: private static final HttpClient client = new HttpClient(
057: new MultiThreadedHttpConnectionManager());
058:
059: // cache key prefixes
060: protected static final String PREFIX = "NlHippoTreeGenerator";
061: protected static final String PREFIX_CHILDREN = PREFIX
062: + "Children:";
063: protected static final String PREFIX_ATTRS = PREFIX + "Attrs:";
064:
065: // some constants
066: private static final String WEBDAV = "webdav:";
067: private static final String HTTP = "http:";
068: private static final String HTTP_STATE_ATTRIBUTE_NAME = "httpstate";
069: private static final String SESSION_USERNAME = "username";
070:
071: // chaching of vectors containing child nodes and of AttributeImpl objects of nodes
072: private ServiceManager servicemanager = null;
073: private EventAwareTransientStore cache;
074:
075: // might store a path to refresh
076: private String refresh = "";
077:
078: // vars to get the list of expanded nodes from the session
079: private HashSet tree = null;
080:
081: // these are some sensible defaults for testing, need to be read from config!
082: private String HTTPHOST = "";
083: private String WEBDAVHOST = "";
084: private String FILESPATH = "";
085: private String EXTRAPATH = "";
086: private String rootDir = "";
087: private String USERNAME = "";
088:
089: private Context m_context;
090:
091: // We will use attributes this time.
092: AttributesImpl emptyAttr = new AttributesImpl();
093:
094: public void service(ServiceManager manager) throws ServiceException {
095: servicemanager = manager;
096: this .cache = (EventAwareTransientStore) servicemanager
097: .lookup(Store.ROLE + "/EventAware");
098: }
099:
100: public void contextualize(Context context) throws ContextException {
101: m_context = context;
102: }
103:
104: /**
105: * called everytime we start using the generator
106: */
107: public void setup(SourceResolver resolver, Map objectModel,
108: String src, Parameters par) throws ProcessingException,
109: SAXException, IOException {
110: super .setup(resolver, objectModel, src, par);
111:
112: // retrieve the hashset from the session
113: Request request = ContextHelper.getRequest(m_context);
114: Session session = request.getSession();
115:
116: this .httpState = (HttpState) session
117: .getAttribute(HTTP_STATE_ATTRIBUTE_NAME);
118:
119: // get the root path to the repo from the config
120: HTTPHOST = (String) session.getAttribute("repositoryroot");
121: HTTPHOST = normalizeUri(HTTPHOST);
122: WEBDAVHOST = HTTPHOST.replaceFirst(HTTP, WEBDAV);
123: EXTRAPATH = par.getParameter("prefix", "");
124: FILESPATH = par.getParameter("filespath", "");
125: refresh = par.getParameter("refresh", "");
126: rootDir = par.getParameter("perspective", "");
127: USERNAME = ((String) session.getAttribute(SESSION_USERNAME))
128: + ":";
129:
130: if (session == null) {
131: if (this .getLogger().isDebugEnabled()) {
132: this .getLogger().debug("session is null");
133: }
134: } else {
135: HashSet sessionTree = (HashSet) session.getAttribute("tree"
136: + rootDir);
137:
138: this .tree = (HashSet) sessionTree.clone();
139: }
140: }
141:
142: public void generate() throws SAXException {
143: AttributesImpl attrs = new AttributesImpl();
144: attrs.addAttribute("", "path", "path", "", EXTRAPATH + rootDir);
145: attrs.addAttribute("", "expanded", "expanded", "", "false");
146:
147: contentHandler.startDocument();
148: contentHandler.startElement("", "node", "node", attrs);
149:
150: try {
151: generalRecurse(rootDir); //start recursing through the tree
152: } catch (IOException e) {
153: System.err
154: .println("ERROR in TreeGenerator.generalRecurse!");
155: }
156:
157: contentHandler.endElement("", "node", "node");
158: contentHandler.endDocument();
159: }
160:
161: // generally recurse through tree
162: private void generalRecurse(String path) throws IOException,
163: SAXException {
164:
165: if (this .getLogger().isDebugEnabled()) {
166: this .getLogger().debug("refreshing: " + refresh);
167: }
168: // if we want to refresh a subtree or the node is not in the cache
169: if (refresh.equals(path) || !recurseCACHE(path))
170: recurseDAV(path); // start getting it from the repo
171: }
172:
173: private boolean recurseCACHE(String path) throws IOException,
174: SAXException {
175: String longpath = PREFIX_CHILDREN + USERNAME + HTTPHOST
176: + FILESPATH + path;
177:
178: // if its not in cache
179: if (!cache.containsKey(longpath))
180: return false;
181:
182: Vector children = (Vector) cache.get(longpath);
183:
184: Iterator it = children.iterator();
185:
186: //test if we have all children in the cache
187: while (it.hasNext())
188: if (!cache.containsKey(PREFIX_ATTRS + USERNAME + HTTPHOST
189: + FILESPATH + it.next().toString()))
190: return false;
191:
192: // we have a hit!
193: //System.out.println("CACHE HIT! for "+path);
194:
195: AttributesImpl attrs = null;
196: String folderName = null;
197: boolean expanded = false;
198:
199: // reset iterator
200: it = children.iterator();
201:
202: while (it.hasNext()) {
203:
204: // extract resource name
205: folderName = (String) it.next();
206:
207: // extract some attributes
208: expanded = this .tree.contains(folderName);
209: attrs = (AttributesImpl) cache.get(PREFIX_ATTRS + USERNAME
210: + HTTPHOST + FILESPATH + folderName);
211:
212: // set other attributes
213: if (expanded)
214: attrs.setValue(9, "true"); //expanded
215: else
216: attrs.setValue(9, "false"); //expanded
217:
218: contentHandler.startElement("", "node", "node", attrs);
219:
220: if (expanded) // recurse
221: generalRecurse(folderName);
222:
223: contentHandler.endElement("", "node", "node");
224:
225: }
226:
227: return true;
228: }
229:
230: private void recurseDAV(String path) throws IOException,
231: SAXException {
232: String longPath = HTTPHOST + FILESPATH + path;
233: String longCachePath = WEBDAVHOST + FILESPATH + path;
234:
235: if (this .getLogger().isDebugEnabled()) {
236: this .getLogger().debug(
237: "doing propfind on " + longPath + " ...");
238: }
239:
240: PropFindMethod method = new PropFindMethod(longPath, 1);
241: Date d = new Date();
242:
243: int state = client.executeMethod(method.getHostConfiguration(),
244: method, httpState);
245:
246: Date end = new Date();
247:
248: if (this .getLogger().isDebugEnabled()) {
249: this .getLogger().debug(
250: "Tree Generator took "
251: + (end.getTime() - d.getTime())
252: + "ms for profind on " + path);
253: }
254:
255: if (state != 207) // an error occured
256: {
257: if (this .getLogger().isDebugEnabled()) {
258: this .getLogger().debug("reponse state is: " + state);
259: }
260: Header[] headers = method.getResponseHeaders();
261: for (int i = 0; i < headers.length; i++) {
262: if (this .getLogger().isDebugEnabled()) {
263: this .getLogger().debug(
264: "header: " + headers[i].toString());
265: }
266: }
267:
268: return;
269: }
270:
271: HashMap propmap = new HashMap();
272:
273: String folder = null;
274: String folderName = null;
275: boolean expanded = false;
276:
277: Vector children = new Vector(); // to be put to cache later
278:
279: Enumeration enums = method.getAllResponseURLs();
280:
281: while (enums.hasMoreElements()) {
282:
283: // make local
284: AttributesImpl attrs = new AttributesImpl();
285: attrs.addAttribute("", "expirationdate", "expirationdate",
286: "", "");
287: attrs.addAttribute("", "scheduledpublicationdate",
288: "scheduledpublicationdate", "", "");
289: attrs.addAttribute("", "requestedpublicationdate",
290: "requestedpublicationdate", "", "");
291: attrs.addAttribute("", "publicationdate",
292: "publicationdate", "", "");
293: attrs.addAttribute("", "index", "index", "", "");
294: attrs.addAttribute("", "lastmodified", "lastmodified", "",
295: "");
296: attrs.addAttribute("", "type", "type", "", "");
297: attrs.addAttribute("", "caption", "caption", "", "");
298: attrs.addAttribute("", "path", "path", "", "");
299: attrs.addAttribute("", "expanded", "expanded", "", "");
300:
301: // extract resource name
302: folder = (String) enums.nextElement();
303: folderName = getFoldername(folder);
304:
305: if (folderName.indexOf(".") > -1 || folderName.equals(path)) //skip files
306: continue;
307:
308: // extract some attributes
309: expanded = this .tree.contains(folderName);
310: propmap.clear();
311:
312: Enumeration props = method.getResponseProperties(folder);
313: Property p = null;
314: String name = null;
315: String value = null;
316:
317: while (props.hasMoreElements()) {
318: p = (Property) props.nextElement();
319:
320: name = p.getLocalName();
321: value = p.getPropertyAsString();
322: propmap.put(name, value);
323:
324: }
325:
326: // set mapped props
327: setAttr(attrs, propmap, 0, "expirationdate", null);
328: setAttr(attrs, propmap, 1, "scheduledpublicationdate", null);
329: setAttr(attrs, propmap, 2, "requestedpublicationdate", null);
330: setAttr(attrs, propmap, 3, "publicationdate", null);
331: setAttr(attrs, propmap, 4, "index", null);
332: setAttr(attrs, propmap, 5, "getlastmodified", null); //lastmodified
333: setAttr(attrs, propmap, 6, "type", null);
334: setAttr(attrs, propmap, 7, "caption", "displayname");
335:
336: // set other attributes
337: attrs.setValue(8, folder); //path
338: if (expanded)
339: attrs.setValue(9, "true"); //expanded
340: else
341: attrs.setValue(9, "false"); //expanded
342:
343: // save values to the cache
344: cache.store(new NamedEvent(WEBDAVHOST + FILESPATH
345: + folderName), PREFIX_ATTRS + USERNAME + HTTPHOST
346: + FILESPATH + folderName, attrs);
347:
348: contentHandler.startElement("", "node", "node", attrs);
349:
350: if (expanded) // recurse only from dav now
351: recurseDAV(folderName);
352:
353: contentHandler.endElement("", "node", "node");
354:
355: children.add(folderName);
356: }
357:
358: // put children to cache
359: cache.store(new NamedEvent(longCachePath), PREFIX_CHILDREN
360: + USERNAME + longPath, children);
361:
362: if (this .getLogger().isDebugEnabled()) {
363: this .getLogger().debug("responses done");
364: }
365:
366: }
367:
368: private void setAttr(AttributesImpl attrs, HashMap map, int index,
369: String key, String key2) {
370: if (map.containsKey(key)) {
371: String value = (String) map.get(key);
372:
373: if ((value == null || value.equals("")) && key2 != null
374: && map.containsKey(key2)) {
375: value = (String) map.get(key2);
376: }
377:
378: attrs.setValue(index, value);
379:
380: return;
381: }
382:
383: if (map.containsKey(key2)) {
384: String value = (String) map.get(key2);
385: attrs.setValue(index, value);
386: }
387: }
388:
389: private String getFoldername(String uri) {
390: int len = EXTRAPATH.length();
391:
392: if (uri.length() > len)
393: return uri.substring(len);
394: else
395: return "/??";
396: }
397:
398: private static final String normalizeUri(String uri) {
399: if (uri.startsWith(WEBDAV)) {
400: return HTTP + uri.substring(WEBDAV.length());
401: }
402: return uri;
403: }
404:
405: }
|