/* * Domain.java * * This work is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * This work is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * Copyright (c) 2004-2006 Per Cederberg. All rights reserved. */ package org.liquidsite.core.content; import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import org.liquidsite.core.data.DataObjectException; import org.liquidsite.core.data.DataSource; import org.liquidsite.core.data.DomainAttributeData; import org.liquidsite.core.data.DomainAttributePeer; import org.liquidsite.core.data.DomainData; import org.liquidsite.core.data.DomainPeer; import org.liquidsite.core.data.DomainSizeData; import org.liquidsite.core.data.DomainSizePeer; import org.liquidsite.util.log.Log; /** * A resource and user domain. * * @author Per Cederberg, <per at percederberg dot net> * @version 1.0 */ public class Domain extends PersistentObject implements Comparable { /** * The class logger. */ private static final Log LOG = new Log(Domain.class); /** * The mail sender address attribute name. */ private static final String MAIL_FROM_ATTRIBUTE = "MAIL.FROM"; /** * The host attribute name. */ private static final String HOST_ATTRIBUTE = "HOST."; /** * The permitted domain name characters. */ public static final String NAME_CHARS = UPPER_CASE + NUMBERS + BINDERS + "."; /** * The permitted host name characters. */ public static final String HOST_NAME_CHARS = LOWER_CASE + NUMBERS + BINDERS + "."; /** * The domain data object. */ private DomainData data; /** * The domain attributes. The attributes are indexed by their * unique names. All attributes are read from and stored to * the database upon reading and writing the domain. */ private HashMap attributes; /** * Returns an array of all domains in the database. * * @param manager the content manager to use * * @return an array of all domains in the database * * @throws ContentException if the database couldn't be accessed * properly */ static Domain[] findAll(ContentManager manager) throws ContentException { DataSource src = getDataSource(manager); ArrayList list; Domain[] res; try { list = DomainPeer.doSelectAll(src); res = new Domain[list.size()]; for (int i = 0; i < list.size(); i++) { res[i] = new Domain(manager, (DomainData) list.get(i), src); } } catch (DataObjectException e) { LOG.error(e.getMessage()); throw new ContentException(e); } finally { src.close(); } return res; } /** * Returns a domain with a specified name. * * @param manager the content manager to use * @param name the domain name * * @return the domain found, or * null if no matching domain existed * * @throws ContentException if the database couldn't be accessed * properly */ static Domain findByName(ContentManager manager, String name) throws ContentException { DataSource src = getDataSource(manager); DomainData data; try { data = DomainPeer.doSelectByName(src, name); } catch (DataObjectException e) { LOG.error(e.getMessage()); throw new ContentException(e); } finally { src.close(); } if (data == null) { return null; } else { return new Domain(manager, data, src); } } /** * Creates a new domain with default values. * * @param manager the content manager to use * @param name the domain name */ public Domain(ContentManager manager, String name) { super(manager, false); this.data = new DomainData(); this.data.setString(DomainData.NAME, name); this.data.setDate(DomainData.CREATED, new Date()); this.data.setDate(DomainData.MODIFIED, new Date()); this.attributes = new HashMap(); } /** * Creates a new domain from a data object. This constructor will * also read all domain attributes from the database. * * @param manager the content manager to use * @param data the domain data object * @param src the data source to use * * @throws ContentException if the database couldn't be accessed * properly */ private Domain(ContentManager manager, DomainData data, DataSource src) throws ContentException { super(manager, true); this.data = data; this.attributes = new HashMap(); try { doReadAttributes(src); } catch (DataObjectException e) { LOG.error(e.getMessage()); throw new ContentException(e); } } /** * Checks if this domain equals another object. This method will * only return true if the other object is a domain with the same * name. * * @param obj the object to compare with * * @return true if the other object is an identical domain, or * false otherwise */ public boolean equals(Object obj) { if (obj instanceof Domain) { return getName().equals(((Domain) obj).getName()); } else { return false; } } /** * Compares this object with the specified object for order. * Returns a negative integer, zero, or a positive integer as * this object is less than, equal to, or greater than the * specified object. * * @param obj the object to compare to * * @return a negative integer, zero, or a positive integer as * this object is less than, equal to, or greater than * the specified object * * @throws ClassCastException if the object isn't a Domain object */ public int compareTo(Object obj) throws ClassCastException { return compareTo((Domain) obj); } /** * Compares this object with the specified domain for order. * Returns a negative integer, zero, or a positive integer as * this object is less than, equal to, or greater than the * specified object. The ordering is based on domain name. * * @param domain the domain to compare to * * @return a negative integer, zero, or a positive integer as * this object is less than, equal to, or greater than * the specified object */ public int compareTo(Domain domain) { return getName().compareTo(domain.getName()); } /** * Returns a string representation of this object. * * @return a string representation of this object */ public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("Domain: "); if (getDescription().equals("")) { buffer.append(getName()); } else { buffer.append(getDescription()); } return buffer.toString(); } /** * Returns the unique domain name. * * @return the unique domain name */ public String getName() { return data.getString(DomainData.NAME); } /** * Returns the domain description. * * @return the domain description */ public String getDescription() { return data.getString(DomainData.DESCRIPTION); } /** * Sets the domain description. * * @param description the new description */ public void setDescription(String description) { data.setString(DomainData.DESCRIPTION, description); } /** * Returns the domain creation date. * * @return the domain creation date */ public Date getCreatedDate() { return data.getDate(DomainData.CREATED); } /** * Returns the domain last modification date. * * @return the domain last modification date */ public Date getModifiedDate() { return data.getDate(DomainData.MODIFIED); } /** * Returns the mail sender address. This address will be used * for all mails sent from the domain. If this address is not set, * the server default address should be used. * * @return the mail sender address, or * null for none */ public String getMailFrom() { return (String) attributes.get(MAIL_FROM_ATTRIBUTE); } /** * Sets the mail sender address. This address will be used for * all mails sent from the domain. If this address is not set, * the server default address should be used. The address should * be formatted as a correct internet mail address, optionally * containing both the name and email address. * * @param address the new address, or null to remove * * @see javax.mail.internet.InternetAddress */ public void setMailFrom(String address) { if (address == null || address.trim().length() == 0) { attributes.remove(MAIL_FROM_ATTRIBUTE); } else { attributes.put(MAIL_FROM_ATTRIBUTE, address); } } /** * Returns the permission list applicable to this domain object. * If the object has no permissions an empty permission list will * be returned. * * @return the permission list for this object * * @throws ContentException if the database couldn't be accessed * properly */ public PermissionList getPermissions() throws ContentException { return getContentManager().getPermissions(this); } /** * Returns the hosts belonging to this domain. The returned list can be * modified freely, as it is only a copy of the actual host data. * * @return an array of hosts in this domain * * @see DomainHost */ public ArrayList getHosts() { ArrayList list = new ArrayList(); Iterator iter = attributes.keySet().iterator(); String name; String str; while (iter.hasNext()) { name = (String) iter.next(); if (name.startsWith(HOST_ATTRIBUTE)) { str = name.substring(HOST_ATTRIBUTE.length()); list.add(new DomainHost(str, (String) attributes.get(name))); } } return list; } /** * Adds or overwrites a host in the domain. * * @param name the host name * @param description the host description */ public void addHost(String name, String description) { attributes.put(HOST_ATTRIBUTE + name, description); } /** * Removes a host belonging to the domain. * * @param name the host name */ public void removeHost(String name) { attributes.remove(HOST_ATTRIBUTE + name); } /** * Removes all hosts belonging to the domain. */ public void removeAllHosts() { Iterator iter = attributes.keySet().iterator(); String name; while (iter.hasNext()) { name = (String) iter.next(); if (name.startsWith(HOST_ATTRIBUTE)) { iter.remove(); } } } /** * Returns the domain file directory. This directory is composed * of the application file directory and the domain name. Note * that this method will create the domain directory if it does * not already exist. * * @return the domain file directory * * @throws ContentException if the domain file directory wasn't * found or couldn't be created */ public File getDirectory() throws ContentException { File dir; dir = getContentManager().getBaseDir(); if (dir == null) { throw new ContentException( "application base file directory not configured"); } dir = new File(dir, getName()); try { if (!dir.exists() && !dir.mkdirs()) { throw new ContentException( "couldn't create domain file directory"); } } catch (SecurityException e) { throw new ContentException( "access denied while creating domain file directory"); } return dir; } /** * Calculates the approximate size of a domain per category. This * calculation will sum the the size of all files in the domain with * an approximate lower estimate for the size of all content in the * domain. All values are calculated per content category. * * @return a list with domain size objects * * @throws ContentException if the database couldn't be accessed * properly * * @see DomainSize */ public ArrayList getSize() throws ContentException { ArrayList res = new ArrayList(); DataSource src = getDataSource(getContentManager()); ArrayList list; DomainSizeData data; try { list = DomainSizePeer.doSelectByDomain(src, getName()); } catch (DataObjectException e) { LOG.error(e.getMessage()); throw new ContentException(e); } finally { src.close(); } for (int i = 0; i < list.size(); i++) { data = (DomainSizeData) list.get(i); if (data.getInt(DomainSizeData.CATEGORY) == Content.FILE_CATEGORY) { res.add(new DomainSize(data, getSize(getDirectory()))); } else { res.add(new DomainSize(data)); } } return res; } /** * Calculates the size of a file or a directory. For directories * the sizes of all contained files will be summed and returned. * * @param file the file or directory * * @return the size in bytes of the file or directory * * @throws ContentException if the file or directory couldn't be read * properly */ private long getSize(File file) throws ContentException { long size = 0; File[] files; try { if (file.isDirectory()) { files = file.listFiles(); for (int i = 0; files != null && files.length > i; i++) { size += getSize(files[i]); } } else { size = file.length(); } } catch (SecurityException e) { throw new ContentException( "access denied while reading domain file directory"); } return size; } /** * Validates the object data before writing to the database. * * @throws ContentException if the object data wasn't valid */ protected void doValidate() throws ContentException { Iterator iter; String name; Domain domain; if (!isPersistent()) { validateSize("domain name", getName(), 1, 30); validateChars("domain name", getName(), NAME_CHARS); if (getContentManager().getDomain(getName()) != null) { throw new ContentException("domain '" + getName() + "' already exists"); } } validateSize("domain description", getDescription(), 0, 100); iter = attributes.keySet().iterator(); while (iter.hasNext()) { name = (String) iter.next(); if (name.startsWith(HOST_ATTRIBUTE)) { name = name.substring(HOST_ATTRIBUTE.length()); validateSize("host name", name, 1, 100); validateChars("host name", name, HOST_NAME_CHARS); domain = getContentManager().getHostDomain(name); if (domain != null && !domain.equals(this)) { throw new ContentException("host '" + name + "' already exists"); } } } } /** * Inserts the object data into the database. If the restore flag * is set, no automatic changes should be made to the data before * writing to the database. * * @param src the data source to use * @param user the user performing the operation * @param restore the restore flag * * @throws ContentException if the database couldn't be accessed * properly */ protected void doInsert(DataSource src, User user, boolean restore) throws ContentException { data.setDate(DomainData.CREATED, new Date()); data.setDate(DomainData.MODIFIED, new Date()); try { DomainPeer.doInsert(src, data); doWriteAttributes(src); } catch (DataObjectException e) { LOG.error(e.getMessage()); throw new ContentException(e); } } /** * Updates the object data in the database. * * @param src the data source to use * @param user the user performing the operation * * @throws ContentException if the database couldn't be accessed * properly */ protected void doUpdate(DataSource src, User user) throws ContentException { data.setDate(DomainData.MODIFIED, new Date()); try { DomainPeer.doUpdate(src, data); doWriteAttributes(src); } catch (DataObjectException e) { LOG.error(e.getMessage()); throw new ContentException(e); } } /** * Deletes the object data from the database. * * @param src the data source to use * @param user the user performing the operation * * @throws ContentException if the database couldn't be accessed * properly */ protected void doDelete(DataSource src, User user) throws ContentException { try { DomainPeer.doDelete(src, data); } catch (DataObjectException e) { LOG.error(e.getMessage()); throw new ContentException(e); } doDelete(getDirectory()); } /** * Deletes a file or directory. All contents of a directory will * be deleted recursively. * * @param file the file or directory to delete * * @throws ContentException if the file or directory couldn't be * deleted properly */ private void doDelete(File file) throws ContentException { File[] files; try { files = file.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { doDelete(files[i]); } } file.delete(); } catch (SecurityException e) { LOG.error("deleting domain directory for " + getName() + ": " + e.getMessage()); throw new ContentException( "access denied while deleting domain file directory"); } } /** * Reads the domain attributes from the database. This method * will add all the attributes to the attributes map. * * @param src the data source to use * * @throws DataObjectException if the data source couldn't be * accessed properly */ private void doReadAttributes(DataSource src) throws DataObjectException { ArrayList list; DomainAttributeData attr; list = DomainAttributePeer.doSelectByDomain(src, getName()); for (int i = 0; i < list.size(); i++) { attr = (DomainAttributeData) list.get(i); attributes.put(attr.getString(DomainAttributeData.NAME), attr.getString(DomainAttributeData.DATA)); } } /** * Writes the domain attributes to the database. This method * will first remove all existing attributes for the domain, * and then insert all the currently existing attributes. * * @param src the data source to use * * @throws DataObjectException if the data source couldn't be * accessed properly */ private void doWriteAttributes(DataSource src) throws DataObjectException { Iterator iter = attributes.keySet().iterator(); DomainAttributeData attr; String name; DomainAttributePeer.doDeleteDomain(src, getName()); while (iter.hasNext()) { name = (String) iter.next(); attr = new DomainAttributeData(); attr.setString(DomainAttributeData.DOMAIN, getName()); attr.setString(DomainAttributeData.NAME, name); attr.setString(DomainAttributeData.DATA, attributes.get(name).toString()); DomainAttributePeer.doInsert(src, attr); } } }