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: package org.apache.wicket.examples.source;
018:
019: import java.io.BufferedReader;
020: import java.io.File;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.io.InputStreamReader;
024: import java.net.JarURLConnection;
025: import java.net.URI;
026: import java.net.URISyntaxException;
027: import java.net.URL;
028: import java.net.URLConnection;
029: import java.util.ArrayList;
030: import java.util.Collections;
031: import java.util.Enumeration;
032: import java.util.List;
033: import java.util.jar.JarEntry;
034: import java.util.jar.JarFile;
035:
036: import org.apache.commons.logging.Log;
037: import org.apache.commons.logging.LogFactory;
038: import org.apache.wicket.Component;
039: import org.apache.wicket.WicketRuntimeException;
040: import org.apache.wicket.ajax.AjaxRequestTarget;
041: import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
042: import org.apache.wicket.markup.html.WebMarkupContainer;
043: import org.apache.wicket.markup.html.WebPage;
044: import org.apache.wicket.markup.html.basic.Label;
045: import org.apache.wicket.markup.html.link.PopupCloseLink;
046: import org.apache.wicket.markup.html.list.ListItem;
047: import org.apache.wicket.markup.html.list.ListView;
048: import org.apache.wicket.model.AbstractReadOnlyModel;
049: import org.apache.wicket.model.IDetachable;
050: import org.apache.wicket.model.PropertyModel;
051: import org.apache.wicket.util.io.IOUtils;
052: import org.apache.wicket.util.lang.PackageName;
053: import org.apache.wicket.util.string.AppendingStringBuffer;
054: import org.apache.wicket.util.string.Strings;
055:
056: import com.uwyn.jhighlight.renderer.Renderer;
057: import com.uwyn.jhighlight.renderer.XhtmlRendererFactory;
058:
059: /**
060: * Displays the resources in a packages directory in a browsable format.
061: *
062: * @author Martijn Dashorst
063: */
064: public class SourcesPage extends WebPage {
065: private static final Log log = LogFactory.getLog(SourcesPage.class);
066:
067: /**
068: * Model for retrieving the source code from the classpath of a packaged
069: * resource.
070: */
071: public class SourceModel extends AbstractReadOnlyModel {
072: /**
073: * Constructor.
074: */
075: public SourceModel() {
076: }
077:
078: /**
079: * Returns the contents of the file loaded from the classpath.
080: *
081: * @return the contents of the file identified by name
082: */
083: public Object getObject() {
084: // name contains the name of the selected file
085: if (Strings.isEmpty(name)) {
086: return "";
087: }
088: BufferedReader br = null;
089: try {
090: StringBuffer sb = new StringBuffer();
091:
092: InputStream resourceAsStream = page
093: .getResourceAsStream(name);
094: if (resourceAsStream == null) {
095: return "Unable to read the source for " + name;
096: }
097: br = new BufferedReader(new InputStreamReader(
098: resourceAsStream));
099:
100: while (br.ready()) {
101: sb.append(br.readLine());
102: sb.append("\n");
103: }
104: int lastDot = name.lastIndexOf('.');
105: if (lastDot != -1) {
106: String type = name.substring(lastDot + 1);
107: Renderer renderer = XhtmlRendererFactory
108: .getRenderer(type);
109: if (renderer != null) {
110: return renderer.highlight(name, sb.toString(),
111: "UTF-8", true);
112: }
113: }
114: return Strings.escapeMarkup(sb.toString(), false, true)
115: .toString().replaceAll("\n", "<br />");
116: } catch (IOException e) {
117: log.error("Unable to read resource stream for: " + name
118: + "; Page=" + page.toString(), e);
119: return "";
120: } finally {
121: IOUtils.closeQuietly(br);
122: }
123: }
124: }
125:
126: /**
127: * Model for retrieving the contents of a package directory from the class
128: * path.
129: */
130: public class PackagedResourcesModel extends AbstractReadOnlyModel
131: implements IDetachable {
132: private final List resources = new ArrayList();
133:
134: /**
135: * Constructor.
136: */
137: public PackagedResourcesModel() {
138: }
139:
140: /**
141: * Clears the list to save space.
142: */
143: protected void onDetach() {
144: resources.clear();
145: }
146:
147: /**
148: * Returns the list of resources found in the package of the page.
149: *
150: * @return the list of resources found in the package of the page.
151: */
152: public Object getObject() {
153: if (resources.isEmpty()) {
154: get(page);
155: // PackageName name = PackageName.forClass(page);
156: // ClassLoader loader = page.getClassLoader();
157: // String path = Strings.replaceAll(name.getName(), ".", "/").toString();
158: // try
159: // {
160: // // gives the urls for each place where the package
161: // // path could be found. There could be multiple
162: // // jar files containing the same package, so each
163: // // jar file has its own url.
164: //
165: // Enumeration urls = loader.getResources(path);
166: // while (urls.hasMoreElements())
167: // {
168: // URL url = (URL)urls.nextElement();
169: //
170: // // the url points to the directory structure
171: // // embedded in the classpath.
172: //
173: // getPackageContents(url);
174: // }
175: // }
176: // catch (IOException e)
177: // {
178: // log.error("Unable to read resource for: " + path, e);
179: // }
180: }
181: return resources;
182: }
183:
184: /**
185: * Retrieves the package contents for the given URL.
186: *
187: * @param packageListing
188: * the url to list.
189: */
190: private void getPackageContents(URL packageListing) {
191: BufferedReader br = null;
192: try {
193: InputStream openStream = packageListing.openStream();
194: if (openStream == null) {
195: return;
196: }
197: br = new BufferedReader(new InputStreamReader(
198: openStream));
199:
200: while (br.ready()) {
201: String listing = br.readLine();
202: String extension = Strings.afterLast(listing, '.');
203: if (!listing.endsWith("class")) {
204: resources.add(listing);
205: }
206: }
207: } catch (IOException e) {
208: log.error("Unable to get package content: "
209: + packageListing.toString(), e);
210: } finally {
211: IOUtils.closeQuietly(br);
212: }
213: }
214:
215: private final void addResources(final Class scope,
216: final AppendingStringBuffer relativePath, final File dir) {
217: File[] files = dir.listFiles();
218: for (int i = 0; i < files.length; i++) {
219: File file = files[i];
220: if (file.isDirectory()) {
221: addResources(scope, new AppendingStringBuffer(
222: relativePath).append(file.getName())
223: .append('/'), file);
224: } else {
225: String name = file.getName();
226: String extension = Strings.afterLast(name, '.');
227: if (!name.endsWith("class")) {
228: resources.add(relativePath + name);
229: }
230:
231: }
232: }
233: }
234:
235: private void get(Class scope) {
236: String packageRef = Strings.replaceAll(
237: PackageName.forClass(scope).getName(), ".", "/")
238: .toString();
239: ClassLoader loader = scope.getClassLoader();
240: try {
241: // loop through the resources of the package
242: Enumeration packageResources = loader
243: .getResources(packageRef);
244: while (packageResources.hasMoreElements()) {
245: URL resource = (URL) packageResources.nextElement();
246: URLConnection connection = resource
247: .openConnection();
248: if (connection instanceof JarURLConnection) {
249: JarFile jf = ((JarURLConnection) connection)
250: .getJarFile();
251: scanJarFile(scope, packageRef, jf);
252: } else {
253: String absolutePath = scope.getResource("")
254: .toExternalForm();
255: File basedir;
256: URI uri;
257: try {
258: uri = new URI(absolutePath);
259: } catch (URISyntaxException e) {
260: throw new RuntimeException(e);
261: }
262: try {
263: basedir = new File(uri);
264: } catch (IllegalArgumentException e) {
265: log
266: .debug("Can't construct the uri as a file: "
267: + absolutePath);
268: // if this is throwen then the path is not really a file. but could be a zip.
269: String jarZipPart = uri
270: .getSchemeSpecificPart();
271: // lowercased for testing if jar/zip, but leave the real filespec unchanged
272: String lowerJarZipPart = jarZipPart
273: .toLowerCase();
274: int index = lowerJarZipPart.indexOf(".zip");
275: if (index == -1)
276: index = lowerJarZipPart.indexOf(".jar");
277: if (index == -1)
278: throw e;
279:
280: String filename = jarZipPart.substring(0,
281: index + 4); // 4 = len of ".jar" or ".zip"
282: log.debug("trying the filename: "
283: + filename
284: + " to load as a zip/jar.");
285: JarFile jarFile = new JarFile(filename,
286: false);
287: scanJarFile(scope, packageRef, jarFile);
288: return;
289: }
290: if (!basedir.isDirectory()) {
291: throw new IllegalStateException(
292: "unable to read resources from directory "
293: + basedir);
294: }
295: addResources(scope,
296: new AppendingStringBuffer(), basedir);
297: }
298: }
299: } catch (IOException e) {
300: throw new WicketRuntimeException(e);
301: }
302: Collections.sort(resources);
303: return;
304: }
305:
306: private void scanJarFile(Class scope, String packageRef,
307: JarFile jf) {
308: Enumeration enumeration = jf.entries();
309: while (enumeration.hasMoreElements()) {
310: JarEntry je = (JarEntry) enumeration.nextElement();
311: String name = je.getName();
312: if (name.startsWith(packageRef)) {
313: name = name.substring(packageRef.length() + 1);
314: String extension = Strings.afterLast(name, '.');
315: if (!name.endsWith("class")) {
316: resources.add(name);
317: }
318: }
319: }
320: }
321: }
322:
323: /**
324: * Displays the resources embedded in a package in a list.
325: */
326: public class FilesBrowser extends WebMarkupContainer {
327: /**
328: * Constructor.
329: *
330: * @param id
331: * the component identifier
332: */
333: public FilesBrowser(String id) {
334: super (id);
335: ListView lv = new ListView("file",
336: new PackagedResourcesModel()) {
337: protected void populateItem(ListItem item) {
338: AjaxFallbackLink link = new AjaxFallbackLink(
339: "link", item.getModel()) {
340: public void onClick(AjaxRequestTarget target) {
341: setName(getModelObjectAsString());
342: target.addComponent(codePanel);
343: target.addComponent(filename);
344: }
345: };
346: link.add(new Label("name", item
347: .getModelObjectAsString()));
348: item.add(link);
349: }
350: };
351: add(lv);
352: }
353: }
354:
355: /**
356: * Container for displaying the source of the selected page, resource or
357: * other element from the package.
358: */
359: public class CodePanel extends WebMarkupContainer {
360: /**
361: * Constructor.
362: *
363: * @param id
364: * the component id
365: */
366: public CodePanel(String id) {
367: super (id);
368: Label code = new Label("code", new SourceModel());
369: code.setEscapeModelStrings(false);
370: code.setOutputMarkupId(true);
371: add(code);
372: }
373: }
374:
375: /**
376: * The selected name of the packaged resource to display.
377: */
378: private String name;
379:
380: /**
381: * The class of the page of which the sources need to be displayed.
382: */
383: private Class page;
384:
385: /**
386: * The panel for setting the ajax calls.
387: */
388: private Component codePanel;
389:
390: private Label filename;
391:
392: /**
393: * Sets the name.
394: *
395: * @param name
396: * the name to set.
397: */
398: public void setName(String name) {
399: this .name = name;
400: }
401:
402: /**
403: * Gets the name.
404: *
405: * @return the name.
406: */
407: public String getName() {
408: return name;
409: }
410:
411: /**
412: * Default constructor, only used for test purposes.
413: */
414: public SourcesPage() {
415: this (SourcesPage.class);
416: }
417:
418: /**
419: * Constructor.
420: *
421: * @param page
422: * the page where the sources need to be shown from.
423: */
424: public SourcesPage(Class page) {
425: this .page = page;
426:
427: filename = new Label("filename",
428: new PropertyModel(this , "name"));
429: filename.setOutputMarkupId(true);
430: add(filename);
431: codePanel = new CodePanel("codepanel").setOutputMarkupId(true);
432: add(codePanel);
433: add(new FilesBrowser("filespanel"));
434: add(new PopupCloseLink("close"));
435: }
436: }
|