Java tutorial
// cinnamon - the Open Enterprise CMS project // Copyright (C) 2007 Dr.-Ing. Boris Horner // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA package server.data; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.Node; import org.hibernate.Hibernate; import org.hibernate.annotations.Type; import org.hibernate.event.PostInsertEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import server.*; import server.dao.*; import server.exceptions.CinnamonConfigurationException; import server.exceptions.CinnamonException; import server.global.Conf; import server.global.ConfThreadLocal; import server.global.Constants; import server.index.IndexAction; import server.index.LuceneBridge; import server.global.PermissionName; import server.helpers.MetasetService; import server.helpers.ObjectTreeCopier; import server.i18n.Language; import server.index.IndexJob; import server.index.Indexable; import server.interfaces.IMetasetJoin; import server.interfaces.IMetasetOwner; import server.interfaces.XmlConvertable; import server.lifecycle.LifeCycleState; import utils.HibernateSession; import utils.ParamParser; import javax.persistence.*; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.util.*; @Entity @Table(name = "objects") public class ObjectSystemData implements Serializable, Ownable, Indexable, XmlConvertable, IMetasetOwner { private static final long serialVersionUID = 1L; public static final String defaultXmlFormatList = "xml|xhtml|dita|ditamap"; static final DAOFactory daoFactory = DAOFactory.instance(DAOFactory.HIBERNATE); @Transient transient Logger log = LoggerFactory.getLogger(this.getClass()); @Column(name = "contentpath", length = 255, // length 255 should be enough, as we use a 128 Bit UUID for the file/folder structure. nullable = true) private String contentPath = null; @Column(name = "contentsize", nullable = true) private Long contentSize = null; @Column(name = "name", length = Constants.NAME_LENGTH, nullable = false) private String name = ""; @OneToOne @JoinColumn(name = "pre_id", nullable = true) private ObjectSystemData predecessor; @ManyToOne @JoinColumn(name = "root_id", nullable = true) // IMPLEMENTATION: workflow-objects set root to null. // TODO: fix workflow: root_id should be now NOT NULL. // TODO: fix OSD creation. private ObjectSystemData root; @Id @GeneratedValue @Column(name = "id") private long id = 0; @ManyToOne @JoinColumn(name = "creator_id", nullable = false) private User creator; @ManyToOne @JoinColumn(name = "locked_by", nullable = true) private User locked_by; @ManyToOne @JoinColumn(name = "modifier_id", nullable = false) private User modifier; @ManyToOne @JoinColumn(name = "owner_id", nullable = true) private User owner; @Temporal(TemporalType.TIMESTAMP) @Column(name = "created", nullable = false) private Date created = new Date(); @Temporal(TemporalType.TIMESTAMP) @Column(name = "modified", nullable = false) private Date modified = new Date(); @ManyToOne @JoinColumn(name = "lang_id", nullable = false) private Language language; @ManyToOne @JoinColumn(name = "acl_id", nullable = false) private Acl acl; @ManyToOne @JoinColumn(name = "parent_id", nullable = false) private Folder parent; //private ObjectSystemData pre_; @ManyToOne @JoinColumn(name = "format_id", nullable = true) private server.Format format; @ManyToOne @JoinColumn(name = "type_id", nullable = false) private ObjectType type; @Column(name = "appname", length = 255) private String appName = ""; @Column(name = "metadata", length = Constants.METADATA_SIZE, nullable = false) @Type(type = "text") private String metadata = "<meta/>"; @Column(name = "procstate", length = 128) private String procstate; @Column(name = "latesthead", nullable = false) private Boolean latestHead; @Column(name = "latestbranch", nullable = false) private Boolean latestBranch = true; @Column(name = "version", length = 128, nullable = false) private String version; @Version @Column(name = "obj_version") @SuppressWarnings("unused") private Long obj_version = 0L; @ManyToOne @JoinColumn(name = "lifecycle_state_id", nullable = true) private LifeCycleState state; @OneToMany(mappedBy = "osd", cascade = { CascadeType.PERSIST, CascadeType.REMOVE }) @OrderBy("id") private Set<OsdMetaset> osdMetasets = new HashSet<OsdMetaset>(); public ObjectSystemData() { } /** * Create an OSD with all default settings and version 1. * Caller must only supply three things: * 1. the name; * 2. the user who is going to be owner, modifier and creator; * 3) the target folder wherein to create the object. * Everything else is set to default and can be modified after instantiation. * * @param name the name of the object. * @param user the user who is to be the creator, owner and modifier. * @param parentFolder the folder where the object will be created. The object will * inherit this folder's ACL by default. */ public ObjectSystemData(String name, User user, Folder parentFolder) { EntityManager em = HibernateSession.getLocalEntityManager(); this.name = name; log.debug("set version label = 1"); version = "1"; latestHead = true; log.debug("set root"); setRoot(this); log.debug("set default objectType"); ObjectTypeDAO otDao = daoFactory.getObjectTypeDAO(em); type = otDao.findByName(Constants.OBJTYPE_DEFAULT); log.debug("set parentfolder"); parent = parentFolder; log.debug("set acl to parent-folder's acl"); setAcl(getParent().getAcl()); procstate = Constants.PS_LABEL_CREATED; log.debug("set language to 'und' (undetermined)"); LanguageDAO langDao = daoFactory.getLanguageDAO(em); Language lang = langDao.findByIsoCode("und"); setLanguage(lang); log.debug("set owner, modifier, creator to " + user.getName()); setOwner(user); setModifier(user); setCreator(user); } public ObjectSystemData(Map<String, Object> cmd, User user, boolean initFromPredecessor) { EntityManager em = HibernateSession.getLocalEntityManager(); ObjectSystemDataDAO osdDao = daoFactory.getObjectSystemDataDAO(em); log.debug("predecessor-init"); if (cmd.containsKey("preid")) { Long preId = ParamParser.parseLong((String) cmd.get("preid"), "error.param.pre_id"); ObjectSystemData pred = osdDao.get(preId); if (pred == null) { throw new CinnamonException("error.predecessor.not_found"); } if (initFromPredecessor) { name = pred.getName(); parent = pred.getParent(); metadata = pred.getMetadata(); appName = pred.getAppName(); language = pred.getLanguage(); if (pred.getState() != null) { state = pred.getState().getLifeCycleStateForCopy(); } } this.predecessor = pred; /* * because sys is now the latest obj in the branch, * its predecessor has to loose the latestBranch flag. */ predecessor.setLatestBranch(false); predecessor.setLatestHead(false); // log.debug("flushing after setting predecessor.branches"); } log.debug("set version label"); version = createNewVersionLabel(); log.debug("new version: " + version); latestHead = !version.contains("."); log.debug("set root"); if (predecessor == null) { setRoot(this); } else { setRoot(predecessor.getRoot()); } log.debug("set name,appname,metadata"); if (cmd.containsKey("name")) { setName((String) cmd.get("name")); } if (cmd.containsKey("appname")) { setAppName((String) cmd.get("appname")); } /* * Set ObjectType: * 1. by objtype_id * 2. by objtype string * 3. by predecessor * 4. default_object_type. */ log.debug("set objectType"); ObjectTypeDAO otDao = daoFactory.getObjectTypeDAO(em); if (cmd.containsKey("objtype_id")) { Long otId = ParamParser.parseLong((String) cmd.get("objtype_id"), "error.param.objtype_id"); this.type = otDao.get(otId); } else if (cmd.containsKey("objtype")) { ObjectType objectType = otDao.findByName((String) cmd.get("objtype")); if (objectType == null) { throw new CinnamonException("error.param.objtype"); } else { this.type = objectType; } } else if (predecessor != null) { this.type = predecessor.getType(); } else { this.type = otDao.findByName(Constants.OBJTYPE_DEFAULT); } log.debug("set parentfolder"); if (cmd.containsKey("parentid")) { Long parent_id = Long.parseLong((String) cmd.get("parentid")); if (parent_id != 0) { FolderDAO folderDAO = daoFactory.getFolderDAO(em); parent = folderDAO.get(parent_id); if (parent != null) { setParent(parent); } else { throw new CinnamonException("error.parent_folder.not_found"); } } else { // parent_id == 0 FolderDAO folderDao = daoFactory.getFolderDAO(em); Folder rootFolder = folderDao.findRootFolder(); setParent(rootFolder); } } else if (parent == null) { // note: parent may be set from predecessor. throw new CinnamonException("error.parent_folder.not_found"); } log.debug("set format"); FormatDAO formatDao = daoFactory.getFormatDAO(em); if (cmd.containsKey("format_id")) { Long formatId = ParamParser.parseLong((String) cmd.get("format_id"), "error.param.format_id"); Format format = formatDao.get(formatId); setFormat(format); } else if (cmd.containsKey("format")) { Format format = formatDao.findByName((String) cmd.get("format")); setFormat(format); } log.debug("set acl"); if (cmd.containsKey("acl_id")) { AclDAO aclDao = daoFactory.getAclDAO(em); Long aclId = ParamParser.parseLong((String) cmd.get("acl_id"), "error.param.acl_id"); Acl acl = aclDao.get(aclId); setAcl(acl); } else { // if no sepcific acl is given, use the parent folder's acl log.debug("set acl to parent-folder's acl"); setAcl(getParent().getAcl()); } procstate = Constants.PS_LABEL_CREATED; /* * Set language to language_id or to 'und' if language is null. */ log.debug("set language"); LanguageDAO langDao = daoFactory.getLanguageDAO(em); if (cmd.containsKey("language_id")) { Long langId = ParamParser.parseLong((String) cmd.get("language_id"), "error.param.language_id"); Language lang = langDao.get(langId); if (lang == null) { throw new CinnamonException("error.param.language_id"); } setLanguage(lang); } else if (getLanguage() == null) { Language lang = langDao.findByIsoCode("und"); setLanguage(lang); } log.debug("set owner, modifier, creator to " + user.getName()); setOwner(user); setModifier(user); setCreator(user); if (cmd.containsKey("metadata")) { // must come last, because MetasetService may persist this object. setMetadata((String) cmd.get("metadata")); } } /** * Create a new OSD based upon "that". The lifecycle state (if not null) is set to the default lifecycle state. * Root, predecessor, format and locked_by are set to null, * version is also 0. You MUST set those to the correct values. * * @param that the source OSD * @param user the active user who will be set as creator / modifier. */ public ObjectSystemData(ObjectSystemData that, User user) { acl = that.getAcl(); appName = that.getAppName(); created = Calendar.getInstance().getTime(); creator = user; owner = user; modified = Calendar.getInstance().getTime(); modifier = user; // format = that.getFormat(); language = that.getLanguage(); latestHead = that.getLatestHead(); latestBranch = that.getLatestBranch(); locked_by = null; metadata = that.getMetadata(); name = that.getName(); parent = that.getParent(); predecessor = null; procstate = that.getProcstate(); type = that.getType(); version = "0"; if (that.getState() != null) { state = that.getState().getLifeCycleStateForCopy(); } } public String getContentPath() { return contentPath; } /** * Set the contentPath directly (and <em>NOT</em> the contentSize). Only for internal use. * * @param contentPath relative path to this object's content */ private void setContentPath(String contentPath) { this.contentPath = contentPath; } /** * Set the contentPath <em>and the contentSize</em>, if the former is a valid String which can * be mapped to a valid File. * * @param contentPath relative path to this object's content. The full path is computed from $cinnamon_data * as set in cinnamon_config.xml) and $repository_name. * @param repository name of the this OSDs repository.. */ public void setContentPath(String contentPath, String repository) { this.contentPath = contentPath; if (contentPath != null) { this.contentSize = contentPath.length() > 0 ? (new File(getFullContentPath(repository))).length() : 0; } else { this.contentSize = null; } } public Acl getAcl() { return acl; } public void setAcl(Acl acl) { this.acl = acl; } public String getAppName() { return appName; } public void setAppName(String appName) { this.appName = appName; } public Long getContentSize() { return contentSize; } public void setContentSize(Long contentSize) { this.contentSize = contentSize; } public Format getFormat() { return format; } public void setFormat(Format format) { this.format = format; } public long getId() { return id; } @SuppressWarnings(value = { "unused" }) private void setId(long id) { this.id = id; } /** * @return the compiled metadata of this element (all metasets collected under one meta root element). */ public String getMetadata() { // for compatibility: return non-empty metadata, otherwise try to compile metasets if (metadata.length() > 8 && getOsdMetasets().size() == 0) { return metadata; } Document doc = DocumentHelper.createDocument(); Element root = doc.addElement("meta"); for (Metaset m : fetchMetasets()) { root.add(Metaset.asElement("metaset", m)); } return doc.asXML(); } /** * @return the compiled metadata of this element (all metasets collected under one meta root element). */ public String getMetadata(List<String> metasetNames) { // for compatibility: return non-empty metadata, otherwise try to compile metasets // if(metadata.length() > 8 && getOsdMetasets().size() == 0){ // return metadata; // } Document doc = DocumentHelper.createDocument(); Element root = doc.addElement("meta"); for (Metaset m : fetchMetasets()) { root.add(Metaset.asElement("metaset", m)); } return metadata; } /** * Set the Metadata on this object. Tries to parse the submitted string * and throws an exception if it is not valid XML.<br/> * Note: this parses the meta-xml into metasets and stores them as such. * * @param metadata the custom metadata */ public void setMetadata(String metadata) { setMetadata(metadata, WritePolicy.BRANCH); } /** * Set the Metadata on this object. Tries to parse the submitted string * and throws an exception if it is not valid XML.<br/> * Note: this parses the meta-xml into metasets and stores them as such. * This method will unlink existing metasets if they are missing from the metadata, * that is, you cannot submit partial metadata to setMetadata. You must set <em>all</em> * metadata if you use this method. (see addMetaset to add an individual metaset) * * @param metadata the custom metadata * @param writePolicy the write policy - what to do if other items already reference a metaset. */ public void setMetadata(String metadata, WritePolicy writePolicy) { try { MetasetService metasetService = new MetasetService(); EntityManager em = HibernateSession.getLocalEntityManager(); if (metadata == null || metadata.trim().length() == 0) { this.metadata = "<meta/>"; for (OsdMetaset om : getOsdMetasets()) { em.remove(om); } } else { Document doc = ParamParser.parseXmlToDocument(metadata, "error.param.metadata"); List<Node> sets = doc.selectNodes("//metaset"); // log.debug("found "+sets.size()+" metaset nodes in:\n"+metadata); if (sets.size() == 0) { this.metadata = metadata; if (osdMetasets.size() > 0) { // delete obsolete metasets: for (Metaset m : fetchMetasets()) { new MetasetService().unlinkMetaset(this, m); } } return; } Set<MetasetType> currentMetasetMap = new HashSet<MetasetType>(); for (Metaset metaset : fetchMetasets()) { // create a set of the currently existing metasets. currentMetasetMap.add(metaset.getType()); } for (Node metasetNode : sets) { String content = metasetNode.detach().asXML(); String metasetTypeName = metasetNode.selectSingleNode("@type").getText(); log.debug("metasetType: " + metasetTypeName); MetasetTypeDAO mtDao = daoFactory.getMetasetTypeDAO(em); MetasetType metasetType = mtDao.findByName(metasetTypeName); if (metasetType == null) { throw new CinnamonException("error.unknown.metasetType", metasetTypeName); } metasetService.createOrUpdateMetaset(this, metasetType, content, writePolicy); currentMetasetMap.remove(metasetType); } for (MetasetType metasetType : currentMetasetMap) { // any metaset that was not found in the metadata parameter will be deleted. metasetService.unlinkMetaset(this, this.fetchMetaset(metasetType.getName())); // somewhat convoluted. } } } catch (Exception e) { log.debug("failed to add metadata:", e); throw new RuntimeException(e); } // remove legacy metadata: this.metadata = "<meta />"; } public IMetasetJoin fetchMetasetJoin(MetasetType type) { EntityManager em = HibernateSession.getLocalEntityManager(); TypedQuery<OsdMetaset> q = em.createQuery( "select o from OsdMetaset o where o.metaset.type=:metasetType and o.osd=:osd", OsdMetaset.class); q.setParameter("metasetType", type); q.setParameter("osd", this); List<OsdMetaset> metasetList = q.getResultList(); log.debug("query for: " + type.getName() + " / osd: " + getId() + " returned #objects: " + metasetList.size()); if (metasetList.size() == 0) { return null; } else if (metasetList.size() > 1) { throw new CinnamonConfigurationException("Found two metasets of the same type in osd #" + getId()); } else { return metasetList.get(0); } } public void addMetaset(Metaset metaset) { // make sure that we do not add a second metaset of the same type: MetasetType metasetType = metaset.getType(); IMetasetJoin metasetJoin = fetchMetasetJoin(metasetType); if (metasetJoin != null) { log.debug("found existing metasetJoin: " + metasetJoin.getId()); throw new CinnamonException( "you tried to add a second metaset of type " + metasetType.getName() + " to " + getId()); } OsdMetaset om = new OsdMetaset(this, metaset); EntityManager em = HibernateSession.getLocalEntityManager(); log.debug("persist metaset " + metaset.getType().getName()); em.persist(om); } public void setName(String name) { this.name = name; } public Folder getParent() { return parent; } public void setParent(Folder parent) { this.parent = parent; } public ObjectSystemData getPredecessor() { return predecessor; } public void setPredecessor(ObjectSystemData previous) { this.predecessor = previous; } /** * @return the first version of this object. */ public ObjectSystemData getRoot() { return root; } public void setRoot(ObjectSystemData root) { this.root = root; } public String getName() { return name; } public ObjectType getType() { return type; } public void setType(ObjectType type) { this.type = type; } public User getCreator() { return creator; } public void setCreator(User user) { this.creator = user; } public Date getCreated() { return created; } public void setCreated(Date created) { this.created = created; } public Date getModified() { return modified; } public void setModified(Date modified) { this.modified = modified; } public Boolean getLatestBranch() { return latestBranch; } public void setLatestBranch(Boolean latestBranch) { this.latestBranch = latestBranch; } public Boolean getLatestHead() { return latestHead; } public void setLatestHead(Boolean latestHead) { this.latestHead = latestHead; } public User getModifier() { return modifier; } public void setModifier(User modifier) { this.modifier = modifier; } public User getOwner() { return owner; } public void setOwner(User owner) { this.owner = owner; } public String getProcstate() { return procstate; } public void setProcstate(String procstate) { this.procstate = procstate; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public User getLocked_by() { return locked_by; } public void setLocked_by(User locked_by) { this.locked_by = locked_by; } /** * createClone: create a copy with created and modified set to current date, <br> * and without an Id (which should be set by the persistence layer when inserting into the db). * * @return the cloned OSD, not yet persisted to the database. Note: this clone lacks the relations * of the original. If you need the relations, you should use original.copyRelations(clone). */ public ObjectSystemData createClone() { ObjectSystemData twin = new ObjectSystemData(); twin.setAcl(getAcl()); twin.setAppName(getAppName()); twin.setContentPath(getContentPath()); twin.setContentSize(getContentSize()); twin.setCreated(Calendar.getInstance().getTime()); twin.setCreator(getCreator()); twin.setFormat(getFormat()); twin.setLanguage(getLanguage()); twin.setLatestBranch(getLatestBranch()); twin.setLatestHead(getLatestHead()); twin.setLocked_by(getLocked_by()); twin.setModified(Calendar.getInstance().getTime()); twin.setModifier(getModifier()); twin.setName(getName()); twin.setParent(getParent()); twin.setPredecessor(getPredecessor()); twin.setProcstate(getProcstate()); twin.setRoot(getRoot()); twin.setType(getType()); twin.setVersion(getVersion()); if (getState() != null) { twin.setState(getState().getLifeCycleStateForCopy()); } twin.setOwner(getOwner()); // will be set later to the correct owner by CmdInterpreter.copy(), if necessary. twin.setMetadata(getMetadata()); // must come last, because it may persist the object via addMetaset. return twin; } /** * @return the language */ public Language getLanguage() { return language; } /** * @param language the language to set */ public void setLanguage(Language language) { this.language = language; } public void updateAccess(User user) { setModifier(user); setModified(Calendar.getInstance().getTime()); } public Document toXML() { Document doc = DocumentHelper.createDocument(); doc.add(convertToElement()); return doc; } /** * Delete the file content of this Data Object. * * @param repository the name of the repository that contains the file. */ public void deleteContent(String repository) { ContentStore.deleteObjectFile(this); setContentSize(null); setContentPath(null); setFormat(null); } public void setContentPathAndFormat(String contentPath, String formatName, String repositoryName) { EntityManager em = HibernateSession.getLocalEntityManager(); FormatDAO formatDAO = daoFactory.getFormatDAO(em); Format newFormat = formatDAO.findByName(formatName); setFormat(newFormat); setContentPathAndFormat(contentPath, newFormat, repositoryName); } public void setContentPathAndFormat(String contentPath, Format newFormat, String repositoryName) { setContentPath(contentPath, repositoryName); // side effect: will set contentSize, if valid. setFormat(newFormat); } /** * Turn a collection of data objects into an XML document. Any exceptions encountered during * serialization are turned into error-Elements which contain the exception's message. * * @param results * @return Document */ static public Document generateQueryObjectResultDocument(Collection<ObjectSystemData> results) { return generateQueryObjectResultDocument(results, false); } /** * Turn a collection of data objects into an XML document. Any exceptions encountered during * serialization are turned into error-Elements which contain the exception's message. * * @param results the source collection of results to be used to generate the XML document. * @param withMetadata if true, include object custom metadata in the output (which can get quite large). * @return Document */ static public Document generateQueryObjectResultDocument(Collection<ObjectSystemData> results, Boolean withMetadata) { Document doc = DocumentHelper.createDocument(); Element root = doc.addElement("objects"); Logger log = LoggerFactory.getLogger(ObjectSystemData.class); for (ObjectSystemData osd : results) { Long id = osd.getId(); log.debug("working on object: " + id); Element data; try { data = osd.convertToElement(); if (withMetadata) { data.add(ParamParser.parseXml(osd.getMetadata(), null)); } root.add(data); } catch (CinnamonException ex) { /* * Note: any exceptions encountered here are probably serious bugs, * which could be caused by corrupted data or faulty serialization * routines. * So, let's report them as errors instead of debug messages. */ log.error("Error serializing object: " + id + " - " + ex.getMessage()); Element error = DocumentHelper.createElement("error").addText(ex.getLocalizedMessage()); error.addElement("id").addText(id.toString()); root.add(error); } } return doc; } public Element toXmlElement(Element root) { Element e = convertToElement(); root.add(e); return e; } /** * Serialize the OSD to a dom4j-Element by name of "object". * User data (in lockedBy, owner, creator, modifier), format and object type are added as * full elements themselves. All other object references will be included as id, for example: * <pre> * {@code * <user><id>453</id><name>foo</name>...</user> * <aclId>1234</aclId> * } * </pre> * * @return the serialized OSD as dom4j-Element */ public Element convertToElement() { Element data = DocumentHelper.createElement("object"); data.addElement("id").addText(String.valueOf(getId())); data.addElement("name").addText(getName()); data.addElement("version").addText(getVersion()); data.addElement("created").addText(ParamParser.dateToIsoString(getCreated())); data.addElement("modified").addText(ParamParser.dateToIsoString(getModified())); data.addElement("procstate").addText(getProcstate()); data.addElement("aclId").addText(String.valueOf(getAcl().getId())); data.addElement("appName").addText(getAppName()); data.addElement("latestHead").addText(getLatestHead().toString()); data.addElement("latestBranch").addText(getLatestBranch().toString()); log.debug("UserAsElementSection"); data.add(User.asElement("lockedBy", getLocked_by())); data.add(User.asElement("owner", getOwner())); data.add(User.asElement("creator", getCreator())); data.add(User.asElement("modifier", getModifier())); log.debug("FormatAsElement"); data.add(Format.asElement("format", getFormat())); log.debug("ObjectTypeAsElement"); data.add(ObjectType.asElement("objectType", getType())); log.debug("nullChecks"); if (getContentSize() != null) { data.addElement("contentsize").addText(String.valueOf(getContentSize())); } else { data.addElement("contentsize"); } if (getParent() != null) { data.addElement("parentId").addText(String.valueOf(getParent().getId())); } else { data.addElement("parentId"); } if (getPredecessor() != null) { data.addElement("predecessorId").addText(String.valueOf(getPredecessor().getId())); } else { data.addElement("predecessorId"); } if (getRoot() != null) { data.addElement("rootId").addText(String.valueOf(getRoot().getId())); } else { data.addElement("rootId"); // TODO: prevent rootId==null on the db level. } log.debug("languageSection"); Element lang = data.addElement("language"); lang.addElement("id").addText(String.valueOf(getLanguage().getId())); lang.addElement("isoCode").addText(getLanguage().getIsoCode()); log.debug("lifecycleSection"); if (getState() == null) { data.addElement("lifeCycleState"); } else { data.addElement("lifeCycleState").addText(String.valueOf(state.getId())); } return data; } /** * Read the content of the OSD and return it as XML for indexing. * * @param repository The repository where the indexable object is located. * @return A string containing the XML version of this object's content. * @see server.index.Indexable */ public String getContent(String repository) { return getContent(repository, null); } /** * @param repository the name of the repository where this object is stored. * @param encoding the encoding of the content. May be null. * @return a string containing the content, in XML format if possible. If the contentSize is null, * an empty content-element is returned. * @see ObjectSystemData#getContent(String) */ public String getContent(String repository, String encoding) { if (contentSize == null) { return "<content/>"; } String fileContent; try { fileContent = utils.ContentReader.readFileAsString(getFullContentPath(repository), encoding); } catch (Exception e) { throw new CinnamonException(e); } return fileContent; } public byte[] getContentAsBytes(String repository) { byte[] fileContent; try { String path = getFullContentPath(repository); if (path == null) { return "<empty />".getBytes(); } log.debug("path to file: " + path); fileContent = utils.ContentReader.readFileAsBytes(path); } catch (Exception e) { throw new CinnamonException(e); } return fileContent; } @Override public String getSystemMetadata() { log.debug("getsystemMeta"); Document doc = DocumentHelper.createDocument(); Element root = doc.addElement("sysMeta"); String className = Hibernate.getClass(this).getName(); root.addAttribute("javaClass", className); root.addAttribute("hibernateId", String.valueOf(getId())); root.addAttribute("id", uniqueId()); // for a given repository, it's unique. log.debug("convertToElement"); root.add(convertToElement()); return doc.asXML(); } /** * @param repositoryName the name of the repository where the object is stored. * @return the complete path to this OSD's content - or null, if no content exists. */ public String getFullContentPath(String repositoryName) { Conf conf = ConfThreadLocal.getConf(); if (contentPath == null) { // log.debug("ContentPath is null"); return null; } String fullContentPath = conf.getDataRoot() + File.separator + repositoryName + File.separator + getContentPath(); // log.debug("fullContentPath: "+fullContentPath); return fullContentPath; } public void copyContent(String repositoryName, ObjectSystemData copy) { String conPath = getContentPath(); if (conPath != null && conPath.length() > 0) { String fullContentPath = getFullContentPath(repositoryName); log.debug("ContentPath: " + fullContentPath + " and Size is: " + getContentSize()); try { String targetPath = ContentStore.copyToContentStore(fullContentPath, repositoryName); log.debug("targetPath = " + targetPath); if (targetPath.length() > 0) { copy.setContentPath(targetPath, repositoryName); } copy.setFormat(getFormat()); } catch (IOException ex) { throw new CinnamonException(ex); } } } public void copyContent(ObjectSystemData copy) { String repositoryName = HibernateSession.getLocalRepositoryName(); copyContent(repositoryName, copy); } @SuppressWarnings("unchecked") public String createNewVersionLabel() { if (getPredecessor() == null) { return "1"; } String predecessorVersion = getPredecessor().getVersion(); String[] branches = predecessorVersion.split("\\."); String lastSegment = branches[branches.length - 1]; String[] lastBranch = lastSegment.split("-"); String lastDescendantVersion; try { log.debug("query for predecessor " + getPredecessor().getId()); EntityManager em = HibernateSession.getLocalEntityManager(); Query q = em.createNamedQuery("findOsdsByPredecessorOrderByIdDesc"); q.setParameter("predecessor", getPredecessor()); List<ObjectSystemData> versions = q.getResultList(); ObjectSystemData lastDescendant; if (versions.size() == 0) { throw new NoResultException(); } else { lastDescendant = versions.get(0); } lastDescendantVersion = lastDescendant.getVersion(); } catch (NoResultException e) { // no object with same predecessor log.debug("no result for last-descendant-query"); String buffer = lastBranch.length == 2 ? lastBranch[1] : lastBranch[0]; String stem = predecessorVersion.substring(0, predecessorVersion.length() - buffer.length()); buffer = String.valueOf(Integer.parseInt(buffer) + 1); return stem + buffer; } log.debug("lastDescendant: " + lastDescendantVersion); String[] lastDescBranches = lastDescendantVersion.split("\\."); if (branches.length == lastDescBranches.length) { // last descendant is the only one so far: create first branch return predecessorVersion + ".1-1"; } String buffer = lastDescBranches[lastDescBranches.length - 1].split("-")[0]; buffer = String.valueOf(Integer.parseInt(buffer) + 1); return predecessorVersion + "." + buffer + "-1"; } public LifeCycleState getState() { return state; } public void setState(LifeCycleState state) { this.state = state; } @Override /* * Implements Comparable interface based on id. * This is used to generate search results with a consistent ordering (where the * results are not ordered by any other parameter). */ public int compareTo(XmlConvertable o) { if (getId() > o.getId()) { return 1; } else if (getId() < o.getId()) { return -1; } return 0; } static public String fetchVersionPredicate(String versions) { String versionPred; if (versions == null || versions.length() == 0 || versions.equals("head")) { versionPred = " and latesthead=true"; } else if (versions.equals("all")) { versionPred = ""; } else if (versions.equals("branch")) { versionPred = " and latestbranch=true"; } else { throw new CinnamonException("error.param.version"); } return versionPred; } /** * Copy a root object and all of its descendants. * * @param source the source object * @param targetFolder the folder in which the copy will be created. * @param activeUser the user who will be owner / modifier of the copied objects. * @return the root object of the new objectTree */ CopyResult copyObjectTree(ObjectSystemData source, Folder targetFolder, User activeUser) { CopyResult cr = new CopyResult(); EntityManager em = HibernateSession.getLocalEntityManager(); // RelationDAO relDao = daoFactory.getRelationDAO(em); ObjectSystemDataDAO osdDao = daoFactory.getObjectSystemDataDAO(em); List<ObjectSystemData> allVersions = osdDao.findAllVersions(source); // List<ObjectSystemData> newTree = new ArrayList<ObjectSystemData>(); // create copies of all versions: ObjectTreeCopier objectTreeCopier = new ObjectTreeCopier(activeUser, targetFolder); ObjectSystemData currentOsd = null; try { log.debug("create full copies of all versions"); for (ObjectSystemData osd : allVersions) { currentOsd = osd; log.debug("create full copy of: " + osd.getId()); ObjectSystemData copy = objectTreeCopier.createFullCopy(osd); objectTreeCopier.getCopyCache().put(osd, copy); log.debug("copy relations"); copyRelations(copy); log.debug("copy content"); copyContent(copy); cr.addObject(copy); } } catch (Exception ex) { /* * If an exception occurs, we must terminate the whole tree, * as in most cases we could only get a stunted version (with missing branches or * missing content etc.) */ for (ObjectSystemData osd : objectTreeCopier.getCopyCache().keySet()) { osd.deleteContent(HibernateSession.getLocalRepositoryName()); osdDao.delete(osd); } cr = new CopyResult(); cr.addFailure(currentOsd, new CinnamonException("Failed to copy tree of OSD.", ex)); } return cr; } /** * Copy relations of an object if the relationType demands it. * * @param target for which the new relations will be created. * @param mode determines which flag on the relation type object is checked */ public void copyRelations(ObjectSystemData target, CopyRelationMode mode) { EntityManager em = HibernateSession.getLocalEntityManager(); RelationDAO relationDao = daoFactory.getRelationDAO(em); List<Relation> relations = relationDao.findAllByLeftOrRight(this, this); for (Relation rel : relations) { RelationType relationType = rel.getType(); if (mode.equals(CopyRelationMode.COPY)) { /* * The relation will only be copied if the cloneOn{left,right}Copy flag is set on the * {left,right} part of the osd which is copied. * Example: html to image relation should (normally) have the clone flag set on the * left, but not on the right part of the relation. If the image is copied, the copy * will not have a relation to the html file. If the html file is copied, the copy * should have a relation to the image. */ if (relationType.getCloneOnLeftCopy() && rel.getLeft().equals(this)) { Relation relCopy = relationDao.findOrCreateRelation(rel.getType(), target, rel.getRight(), rel.getMetadata()); log.debug("created new Relation: " + relCopy); } if (relationType.getCloneOnRightCopy() && rel.getRight().equals(this)) { Relation relCopy = relationDao.findOrCreateRelation(rel.getType(), rel.getLeft(), target, rel.getMetadata()); log.debug("created new Relation: " + relCopy); } } else { if (relationType.getCloneOnLeftVersion() && rel.getLeft().equals(this)) { Relation relCopy = relationDao.findOrCreateRelation(rel.getType(), target, rel.getRight(), rel.getMetadata()); log.debug("created new Relation: " + relCopy); } if (relationType.getCloneOnRightVersion() && rel.getRight().equals(this)) { Relation relCopy = relationDao.findOrCreateRelation(rel.getType(), rel.getLeft(), target, rel.getMetadata()); log.debug("created new Relation: " + relCopy); } } } } public void copyRelations(ObjectSystemData target) { copyRelations(target, CopyRelationMode.COPY); } /** * Get a new filename for the given osd. * First, it filters potentially harmful characters from the object's name. Then, * if a file with osd.name already exists, it will try * to create increasingly specific filenames, by using the version and then the id to differentiate this * filename from other existing files. This method is intended for use when yours is the only thread actually * working in this folder. It is <em>not</em> to be used for generic folders like java.io.tmpdir to construct * "unique" filenames (where you may encounter security problems due to race conditions). * If all fails, create a filename $name_$version_$id_$uuid.$format where uuid is a unique hex string. * * @param path the path where the new File will be created. * @return a filename which is unique inside this path. */ public File createFilenameFromName(File path) { String extension = ""; if (format != null) { // the format *should* be set, but you never know. Perhaps someone created a 0-byte lock file without format. extension = "." + format.getExtension(); } name = name.replaceAll("[^\\w]", "_"); File file = new File(path, name + extension); if (file.exists()) { name = name + "_" + getVersion(); file = new File(path, name + extension); if (file.exists()) { name = name + "_" + getId(); file = new File(path, name + extension); if (file.exists()) { // "Wir knnen auch anders." return new File(path, name + "_" + UUID.randomUUID().toString() + extension); } } } return file; } /** * Check if the content can be considered to be XML. This is used by the LuceneBridge to * determine if it's worth parsing the content with an XML-Parser. Previously we would * try to parse everything, but this is a waste of time and memory (especially when reading * huge files only to have the parser croak on seeing that it's a zip archive etc) * A somewhat crude heuristic (format.contenttype.endsWith("xml")) is used to detect xml content * at the moment. * * @return true if the content is probably xml, false if not. */ public Boolean hasXmlContent() { if (format == null) { return false; } return getFormat().getExtension().toLowerCase().matches(fetchXmlFormatList()); } public Set<OsdMetaset> getOsdMetasets() { return osdMetasets; } public void setOsdMetasets(Set<OsdMetaset> osdMetasets) { this.osdMetasets = osdMetasets; } public Set<Metaset> fetchMetasets() { Set<Metaset> metasets = new HashSet<Metaset>(getOsdMetasets().size()); for (OsdMetaset om : getOsdMetasets()) { metasets.add(om.getMetaset()); } // log.debug("found "+metasets.size()+" metasets"); return metasets; } /** * Fetch a metaset by its given name. Returns null in case there is no such metaset * associated with this object. * * @param name the name of the metaset * @return the metaset or null */ public Metaset fetchMetaset(String name) { Metaset metaset = null; for (Metaset m : fetchMetasets()) { // log.debug("check "+name+" vs. "+m.getType().getName()); if (m.getType().getName().equals(name)) { metaset = m; break; } } return metaset; } public Metaset fetchMetaset(String name, Boolean autocreate) { Metaset metaset = fetchMetaset(name); if ((metaset == null) && autocreate) { MetasetService ms = new MetasetService(); MetasetTypeDAO mtDao = daoFactory.getMetasetTypeDAO(HibernateSession.getLocalEntityManager()); metaset = ms.createOrUpdateMetaset(this, mtDao.findByName(name), null, WritePolicy.BRANCH); } return metaset; } /** * You can add a config entry to define formats that can be parsed by XML/XPath based IndexItems. * For example, DITA files are XML, but have their own extension. * The default xmlFormatList is: "xml|xhtml|dita|ditamap" * * @return a String that may be used as a regex to filter for invalid format extensions<br/> * Example: may return "dita|xml|foo" */ String fetchXmlFormatList() { EntityManager em = HibernateSession.getLocalEntityManager(); ConfigEntryDAO ceDao = daoFactory.getConfigEntryDAO(em); ConfigEntry formatList = ceDao.findByName("xml.format.list"); if (formatList == null) { log.debug("Did not find xml.format.list config entry, returning defaultXmlFormatList."); return defaultXmlFormatList; } Node formatListNode = ParamParser.parseXmlToDocument(formatList.getConfig()).selectSingleNode("//format"); if (formatListNode == null) { log.debug("Did not find format node in xml.format.list config entry, returning defaultXmlFormatlist."); return defaultXmlFormatList; } log.debug("Found formatList: " + formatListNode.getText()); return formatListNode.getText(); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof ObjectSystemData)) return false; ObjectSystemData that = (ObjectSystemData) o; if (acl != null ? !acl.equals(that.acl) : that.acl != null) return false; if (appName != null ? !appName.equals(that.appName) : that.appName != null) return false; if (contentPath != null ? !contentPath.equals(that.contentPath) : that.contentPath != null) return false; if (contentSize != null ? !contentSize.equals(that.contentSize) : that.contentSize != null) return false; if (created != null ? !created.equals(that.created) : that.created != null) return false; if (creator != null ? !creator.equals(that.creator) : that.creator != null) return false; if (format != null ? !format.equals(that.format) : that.format != null) return false; if (language != null ? !language.equals(that.language) : that.language != null) return false; if (latestBranch != null ? !latestBranch.equals(that.latestBranch) : that.latestBranch != null) return false; if (latestHead != null ? !latestHead.equals(that.latestHead) : that.latestHead != null) return false; if (locked_by != null ? !locked_by.equals(that.locked_by) : that.locked_by != null) return false; if (metadata != null ? !metadata.equals(that.metadata) : that.metadata != null) return false; if (modified != null ? !modified.equals(that.modified) : that.modified != null) return false; if (modifier != null ? !modifier.equals(that.modifier) : that.modifier != null) return false; if (name != null ? !name.equals(that.name) : that.name != null) return false; if (owner != null ? !owner.equals(that.owner) : that.owner != null) return false; if (parent != null ? !parent.equals(that.parent) : that.parent != null) return false; if (predecessor != null ? !predecessor.equals(that.predecessor) : that.predecessor != null) return false; if (procstate != null ? !procstate.equals(that.procstate) : that.procstate != null) return false; if (root != null ? !root.equals(that.root) : that.root != null) return false; if (state != null ? !state.equals(that.state) : that.state != null) return false; if (type != null ? !type.equals(that.type) : that.type != null) return false; if (version != null ? !version.equals(that.version) : that.version != null) return false; return true; } @Override public int hashCode() { int result = name != null ? name.hashCode() : 0; result = 31 * result + (created != null ? created.hashCode() : 0); result = 31 * result + (version != null ? version.hashCode() : 0); return result; } public static void fixLatestHeadAndBranch(ObjectSystemData target, List<ObjectSystemData> children) { ObjectSystemData predecessor = target.getPredecessor(); Boolean hasChildren = children.size() > 0; if (hasChildren) { // if target has children: it is not latestBranch. target.setLatestBranch(false); // target *may* be latestHead if the previous head has been deleted // and only child branches remain. Boolean isHead = true; for (ObjectSystemData child : children) { if (child.getVersion().matches("^\\d+$")) { isHead = false; break; } } target.setLatestHead(isHead); } else { // target no children: it is latestBranch target.setLatestBranch(true); if (target.getVersion().matches("^\\d+$")) { target.setLatestHead(true); if (predecessor != null && predecessor.getLatestHead()) { predecessor.setLatestHead(false); } } } // the predecessor cannot be latest branch, that has to be this (or a descendant) node. if (predecessor != null && predecessor.getLatestBranch()) { predecessor.setLatestBranch(false); } } public void updateIndex() { EntityManager em = HibernateSession.getLocalEntityManager(); IndexJobDAO jobDAO = daoFactory.getIndexJobDAO(em); IndexJob indexJob = new IndexJob(this); jobDAO.makePersistent(indexJob); } @PostUpdate public void updateIndexOnCommit() { LocalRepository.addIndexable(this, IndexAction.UPDATE); } @PostPersist public void addToIndexOnCommit() { LocalRepository.addIndexable(this, IndexAction.ADD); } @PostRemove public void removeFromIndex() { LocalRepository.addIndexable(this, IndexAction.REMOVE); } @Override public Long myId() { return id; } @Override public Indexable reload() { EntityManager em = HibernateSession.getLocalEntityManager(); em.refresh(this); return this; } @Override public String uniqueId() { String className = Hibernate.getClass(this).getName(); return className + "@" + getId(); } }