001: package com.ice.jcvsweb.manager;
002:
003: import java.awt.Color;
004: import java.awt.Dimension;
005: import java.awt.Font;
006: import java.awt.Graphics2D;
007: import java.awt.Image;
008: import java.awt.RenderingHints;
009: import java.awt.Transparency;
010: import java.awt.image.BufferedImage;
011: import java.awt.font.TextAttribute;
012: import java.io.File;
013: import java.io.FileInputStream;
014: import java.io.FileOutputStream;
015: import java.io.InputStream;
016: import java.io.IOException;
017: import java.io.OutputStream;
018: import java.util.ArrayList;
019: import java.util.HashMap;
020: import java.util.Iterator;
021: import java.util.Locale;
022:
023: import javax.servlet.ServletContext;
024:
025: import javax.imageio.IIOImage;
026: import javax.imageio.ImageIO;
027: import javax.imageio.ImageWriteParam;
028: import javax.imageio.ImageWriter;
029: import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
030: import javax.imageio.stream.ImageOutputStream;
031:
032: import com.ice.cvsc.CVSRequest;
033:
034: import org.w3c.dom.Document;
035: import org.w3c.dom.Element;
036: import org.w3c.dom.Node;
037: import org.w3c.dom.NodeList;
038: import org.xml.sax.SAXException;
039:
040: import com.ice.jcvsweb.bean.JCVSConfiguration;
041: import com.ice.jcvsweb.bean.JCVSProject;
042: import com.ice.jcvsweb.bean.JCVSProjectDef;
043: import com.ice.jcvsweb.bean.JCVSProjectView;
044: import com.ice.jcvsweb.bean.JCVSUser;
045: import com.ice.jcvsweb.helper.ContextHelper;
046: import com.ice.jcvsweb.helper.XMLHelper;
047:
048: /**
049: * This class Manages our JCVSProjects.
050: *
051: * @author Tim Endres, <a href="mailto:time@jcvs.org">time@jcvs.org</a>
052: * @see com.ice.cvsc
053: */
054:
055: public class JCVSProjectManager {
056: public static final String ATTR_NAME = "jcvsProjectManager";
057:
058: private ServletContext context = null;
059: private JCVSConfiguration config = null;
060: private HashMap projectCache = null;
061:
062: public JCVSProjectManager(ServletContext ctx,
063: JCVSConfiguration config) {
064: this .context = ctx;
065: this .config = config;
066: this .projectCache = new HashMap(32);
067: }
068:
069: public File getProjectConfDir() {
070: return new File(ContextHelper.getRealAbsolutePath(this .context,
071: this .config.getConfigDir()), "project");
072: }
073:
074: public File getDefaultProjectDir() {
075: return new File(ContextHelper.getRealAbsolutePath(this .context,
076: this .config.getDefaultProjectDir()));
077: }
078:
079: public File getProjectLogoDir() {
080: return new File(ContextHelper.getRealAbsolutePath(this .context,
081: this .config.getProjectLogoDir()));
082: }
083:
084: public void ensureInitialState() {
085: File f = this .getProjectConfDir();
086: if (!f.exists()) {
087: if (!f.mkdirs()) {
088: this .context
089: .log("FATAL Could not establish directory '"
090: + f.getPath() + "'");
091: }
092: }
093:
094: f = this .getDefaultProjectDir();
095: if (!f.exists()) {
096: if (!f.mkdirs()) {
097: this .context
098: .log("FATAL Could not establish directory '"
099: + f.getPath() + "'");
100: }
101: }
102:
103: f = this .getProjectLogoDir();
104: if (!f.exists()) {
105: if (!f.mkdirs()) {
106: this .context
107: .log("FATAL Could not establish directory '"
108: + f.getPath() + "'");
109: }
110: }
111:
112: this .ensureDefaultLogo();
113: }
114:
115: private void ensureDefaultLogo() {
116: File projIconF = new File(ContextHelper.getRealAbsolutePath(
117: this .context, this .config.getProjectLogoDir()),
118: "DEFAULT.jpg");
119:
120: if (!projIconF.exists()) {
121: File defIconF = new File(ContextHelper.getRealAbsolutePath(
122: this .context, "WEB-INF/images/logos/DEFAULT.jpg"));
123:
124: try {
125: this .copyBinaryFile(defIconF, projIconF);
126: } catch (IOException ex) {
127: this .context.log("ensuring default logo", ex);
128: }
129: }
130: }
131:
132: public void ensureProjectDirectories(JCVSProject project) {
133: String localDirPath = ContextHelper.getRealAbsolutePath(
134: this .context, project.getDef().getLocalDirectory());
135:
136: File localDir = new File(localDirPath);
137: if (!localDir.exists()) {
138: if (!localDir.mkdirs())
139: this .context.log("[EnsProjDirs] mkdirs('" + localDir
140: + "') failed");
141: }
142:
143: File viewDir = new File(localDir, "views");
144: if (!viewDir.exists()) {
145: if (!localDir.mkdirs())
146: this .context.log("[EnsProjDirs] mkdirs('" + viewDir
147: + "') failed");
148: }
149:
150: File logDir = new File(localDir, "logs");
151: if (!logDir.exists()) {
152: if (!localDir.mkdirs())
153: this .context.log("[EnsProjDirs] mkdirs('" + logDir
154: + "') failed");
155: }
156: }
157:
158: public JCVSProject getProject(String key) {
159: JCVSProject result = (JCVSProject) this .projectCache.get(key);
160:
161: if (result == null) {
162: try {
163: result = this .loadProject(key);
164: } catch (SAXException ex) {
165: // UNDONE HIGH
166: // How do we handle this?!
167: ex.printStackTrace(System.err);
168: }
169:
170: if (result != null) {
171: this .projectCache.put(key, result);
172: }
173: }
174:
175: return result;
176: }
177:
178: public void deleteProject(JCVSProject project) {
179: String key = project.getDef().getKey();
180: String fileName = key + ".xml";
181: File xmlFile = new File(this .getProjectConfDir(), fileName);
182:
183: if (xmlFile.exists() && xmlFile.isFile()) {
184: xmlFile.delete();
185: }
186:
187: this .projectCache.remove(key);
188: }
189:
190: public String[] getProjectList() {
191: ArrayList list = new ArrayList();
192:
193: File confDirF = this .getProjectConfDir();
194: String[] files = this .getProjectConfDir().list();
195: for (int i = 0; files != null && i < files.length; ++i) {
196: if (files[i].toLowerCase().endsWith(".xml")) {
197: String fName = files[i];
198: File f = new File(confDirF, fName);
199: if (f.exists() && f.isFile()) {
200: String key = fName.substring(0,
201: (fName.length() - 4));
202: list.add(key);
203: }
204: }
205: }
206:
207: String[] result = new String[list.size()];
208: list.toArray(result);
209: return result;
210: }
211:
212: public String[] getProjectsOwnedBy(String userName) {
213: ArrayList list = new ArrayList();
214:
215: String[] keys = this .getProjectList();
216: for (int i = 0; i < keys.length; ++i) {
217: JCVSProject project = this .getProject(keys[i]);
218: if (project != null
219: && userName.equals(project.getDef().getOwner())) {
220: list.add(keys[i]);
221: }
222: }
223:
224: String[] result = new String[list.size()];
225: list.toArray(result);
226: return result;
227: }
228:
229: public int getNumberOfProjectsOwnedBy(String userName) {
230: int result = 0;
231:
232: String[] keys = this .getProjectList();
233: for (int i = 0; i < keys.length; ++i) {
234: JCVSProject project = this .getProject(keys[i]);
235: if (project != null
236: && userName.equals(project.getDef().getOwner())) {
237: ++result;
238: }
239: }
240:
241: return result;
242: }
243:
244: public String[] getProjectsViewableBy(JCVSPermManager permMgr,
245: JCVSUser user) {
246: ArrayList list = new ArrayList();
247: boolean restrictAll = this .config.getViewingRestricted();
248:
249: String[] keys = this .getProjectList();
250: for (int i = 0; i < keys.length; ++i) {
251: JCVSProject project = this .getProject(keys[i]);
252: if (project != null
253: && permMgr.getUserCanView(user, project)) {
254: list.add(keys[i]);
255: }
256: }
257:
258: String[] result = new String[list.size()];
259: list.toArray(result);
260: return result;
261: }
262:
263: public boolean getProjectExists(String name) {
264: String fileName = name + ".xml";
265: File xmlFile = new File(this .getProjectConfDir(), fileName);
266: return xmlFile.exists();
267: }
268:
269: public void setProjectIcon(String projectKey, InputStream iconStream)
270: throws IOException {
271: BufferedImage logo = this .resizeIconImage(iconStream,
272: this .config.getMaxLogoWidth(), this .config
273: .getMaxLogoHeight());
274:
275: // UNDONE HIGH
276: // ERROR HANDLING!
277: //
278: if (logo != null) {
279: File projIconF = new File(ContextHelper
280: .getRealAbsolutePath(this .context, this .config
281: .getProjectLogoDir()), projectKey + ".jpg"); // UNDONE config that extension
282:
283: ImageOutputStream ios = ImageIO
284: .createImageOutputStream(projIconF);
285:
286: Iterator iter = ImageIO.getImageWritersByFormatName("jpg");
287: ImageWriter writer = (ImageWriter) iter.next();
288: writer.setOutput(ios);
289:
290: ImageWriteParam iwparam = new JPEGImageWriteParam(Locale
291: .getDefault());
292: iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
293: iwparam.setCompressionQuality(0.90F);
294:
295: IIOImage imgIO = new IIOImage(logo, null, null);
296:
297: writer.write(null, imgIO, iwparam);
298:
299: ios.flush();
300: writer.dispose();
301: ios.close();
302: }
303: }
304:
305: public void setProjectIconDefault(JCVSProject project) {
306: // UNDONE MED
307: // All of these file extensions (".jpg", ".png", etc.) should
308: // be established by configuration somewhere...
309: // Also, move that string to a constant.
310: //
311: File defIconF = new File(ContextHelper.getRealAbsolutePath(
312: this .context, "WEB-INF/images/logos/DEFAULT.jpg"));
313:
314: File projIconF = new File(ContextHelper.getRealAbsolutePath(
315: this .context, this .config.getProjectLogoDir()), project
316: .getDef().getKey()
317: + ".jpg");
318:
319: try {
320: this .copyBinaryFile(defIconF, projIconF);
321: } catch (IOException ex) {
322: this .context
323: .log("Copying default logo to project logo", ex);
324: }
325: }
326:
327: private void copyBinaryFile(File fromF, File toF)
328: throws IOException {
329: FileInputStream fis = null;
330: FileOutputStream fos = null;
331:
332: try {
333: fis = new FileInputStream(fromF);
334: fos = new FileOutputStream(toF);
335: this .copyBinaryFile(fis, fos);
336: } finally {
337: if (fis != null)
338: fis.close();
339: if (fos != null)
340: fos.close();
341: }
342: }
343:
344: private void copyBinaryFile(InputStream is, OutputStream os)
345: throws IOException {
346: if (is != null && os != null) {
347: byte[] buf = new byte[4 * 1024];
348: for (;;) {
349: int numRead = is.read(buf, 0, buf.length);
350:
351: if (numRead == -1)
352: break;
353:
354: os.write(buf, 0, numRead);
355: }
356: } else {
357: throw new IOException((is == null ? "input" : "output")
358: + " stream is null");
359: }
360: }
361:
362: public JCVSProject createProject(String key) throws IOException {
363: return this .createProject(key, "admin");
364: }
365:
366: public JCVSProject createProject(String key, String owner)
367: throws IOException {
368: String fileName = key + ".xml";
369: File xmlFile = new File(this .getProjectConfDir(), fileName);
370:
371: JCVSProject project = new JCVSProject();
372: JCVSProjectDef def = new JCVSProjectDef();
373: project.setDef(def);
374:
375: def.setKey(key);
376: def.setType(JCVSProjectDef.PT_JCVS);
377: def.setOwner(owner);
378: def.setName("");
379: def.setTitle("");
380: def.setCvsRoot("");
381: def.setCvsHost("");
382: def.setCvsLogin("");
383: def.setCvsPassword("");
384: def.setCvsMethod(CVSRequest.METHOD_INETD);
385: def.setHomePage("");
386: def.setJcvsLink("");
387:
388: String defProjDir = this .config.getDefaultProjectDir()
389: + File.separator + key;
390: def.setLocalDirectory(defProjDir);
391:
392: def.setTempDirectory(this .config.getDefaultTempDir());
393:
394: project.saveConfiguration(xmlFile);
395:
396: this .setProjectIconDefault(project);
397:
398: this .ensureProjectDirectories(project);
399:
400: return project;
401: }
402:
403: private JCVSProject loadProject(String key) throws SAXException {
404: JCVSProject result = null;
405:
406: String fileName = key + ".xml";
407: File xmlFile = new File(this .getProjectConfDir(), fileName);
408:
409: Document xmlDoc = XMLHelper.readXMLConfiguration(xmlFile);
410:
411: if (xmlDoc != null) {
412: Element projElem = (Element) XMLHelper.selectNode(xmlDoc
413: .getDocumentElement(), "/jcvs-project");
414:
415: result = new JCVSProject();
416:
417: // Load the child elements...
418: NodeList children = projElem.getChildNodes();
419: for (int ci = 0; ci < children.getLength(); ++ci) {
420: Node n = children.item(ci);
421: if (n.getNodeType() == Node.ELEMENT_NODE) {
422: //
423: // <jcvs-def>
424: //
425: if ("jcvs-def".equals(n.getNodeName())) {
426: JCVSProjectDef def = new JCVSProjectDef();
427:
428: def.setKey(key);
429: def.setProject(result);
430: def.setTempDirectory(this .config
431: .getDefaultTempDir());
432: def.setLocalDirectory(this .config
433: .getDefaultProjectDir());
434:
435: def.loadConfiguration(n);
436:
437: result.setDef(def);
438: }
439: //
440: // <jcvs-view>
441: //
442: else if ("jcvs-view".equals(n.getNodeName())) {
443: JCVSProjectView view = new JCVSProjectView();
444: view.setProject(result);
445: view.loadConfiguration(n);
446: result.addView(view);
447: }
448: //
449: // <mime-mapping>
450: //
451: else if ("mime-mapping".equals(n.getNodeName())) {
452: result.loadMimeMapping(n);
453: }
454: }
455: }
456: }
457:
458: return result;
459: }
460:
461: public void saveProject(JCVSProject project) throws IOException {
462: String fileName = project.getDef().getKey() + ".xml";
463: File xmlFile = new File(this .getProjectConfDir(), fileName);
464:
465: project.saveConfiguration(xmlFile);
466:
467: this .projectCache.put(project.getDef().getKey(), project);
468: }
469:
470: public void clearCache() {
471: this .projectCache.clear();
472: }
473:
474: public void deleteProjectDirectory(JCVSProject project) {
475: this .deleteFileTree(new File(project.getDef()
476: .getLocalDirectory()));
477: }
478:
479: public void deleteViewDirectory(JCVSProject project) {
480: String viewPath = project.getDef().getLocalDirectory()
481: + File.separator + "views";
482:
483: this .deleteFileTree(new File(viewPath));
484: }
485:
486: public void deleteWorkingDirectory(JCVSProjectView view) {
487: String viewDir = ContextHelper.getRealAbsolutePath(
488: this .context, view.getViewDirectory());
489:
490: File viewDirF = new File(viewDir);
491:
492: this .deleteFileTree(viewDirF);
493: }
494:
495: private boolean deleteFileTree(File delF) {
496: if (!delF.exists()) {
497: return true;
498: }
499:
500: if (delF.isFile()) {
501: return delF.delete();
502: } else {
503: String[] files = delF.list();
504: for (int fi = 0; files != null && files.length > 0
505: && fi < files.length; ++fi) {
506: File f = new File(delF, files[fi]);
507: if (!this .deleteFileTree(f)) {
508: return false;
509: }
510: }
511:
512: return delF.delete();
513: }
514: }
515:
516: public BufferedImage resizeIconImage(InputStream imgStream,
517: int maxWidth, int maxHeight) {
518: Image img = null;
519:
520: try {
521: img = ImageIO.read(imgStream);
522: } catch (IOException ex) {
523: this .context.log("reading icon image stream", ex);
524: }
525:
526: return resizeIconImage(img, maxWidth, maxHeight);
527: }
528:
529: public BufferedImage resizeIconImage(Image image, int maxWidth,
530: int maxHeight) {
531: if (image == null) {
532: //
533: // REVIEW
534: //
535: BufferedImage iconImg = new BufferedImage(maxWidth,
536: maxHeight, BufferedImage.TYPE_INT_RGB);
537:
538: HashMap fontAttrs = new HashMap();
539: fontAttrs.put(TextAttribute.FAMILY, "Sans-Serif");
540: fontAttrs.put(TextAttribute.SIZE, new Float(12.0F));
541: fontAttrs.put(TextAttribute.WEIGHT,
542: TextAttribute.WEIGHT_BOLD);
543: Font msgFont = Font.getFont(fontAttrs);
544:
545: Graphics2D g2D = iconImg.createGraphics();
546: g2D.setColor(Color.yellow);
547: g2D.fillRect(0, 0, maxWidth, maxHeight);
548: g2D.setColor(Color.black);
549: g2D.setFont(msgFont);
550: g2D.drawString("Icon upload", 5, 17);
551: g2D.drawString("incomplete.", 5, 34);
552: return iconImg;
553: }
554:
555: int imgW = image.getWidth(null);
556: int imgH = image.getHeight(null);
557: int scaledW = imgW;
558: int scaledH = imgH;
559: if ((imgW > maxWidth) || (imgH > maxHeight)) {
560: float aspW = ((float) maxWidth / (float) imgW);
561: float aspH = ((float) maxHeight / (float) imgH);
562: float aspect = Math.min(aspW, aspH);
563: scaledW = Math.round((float) imgW * aspect);
564: scaledH = Math.round((float) imgH * aspect);
565: }
566:
567: HashMap hints = new HashMap(4);
568: hints.put(RenderingHints.KEY_ANTIALIASING,
569: RenderingHints.VALUE_ANTIALIAS_ON);
570: hints.put(RenderingHints.KEY_RENDERING,
571: RenderingHints.VALUE_RENDER_QUALITY);
572: hints.put(RenderingHints.KEY_COLOR_RENDERING,
573: RenderingHints.VALUE_COLOR_RENDER_QUALITY);
574:
575: BufferedImage iconImg = new BufferedImage(scaledW, scaledH,
576: BufferedImage.TYPE_INT_RGB);
577:
578: Graphics2D g2D = iconImg.createGraphics();
579: g2D.setRenderingHints(hints);
580: g2D.drawImage(image, 0, 0, scaledW, scaledH, null);
581:
582: return iconImg;
583: }
584:
585: }
|