package marubinotto.piggydb.model.entity; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.text.BreakIterator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Stack; import marubinotto.piggydb.model.Fragment; import marubinotto.piggydb.model.FragmentList; import marubinotto.piggydb.model.FragmentRelation; import marubinotto.piggydb.model.Tag; import marubinotto.piggydb.model.TagRepository; import marubinotto.piggydb.model.auth.User; import marubinotto.piggydb.model.exception.AuthorizationException; import marubinotto.piggydb.model.exception.InvalidTaggingException; import marubinotto.piggydb.model.exception.InvalidTitleException; import marubinotto.util.Assert; import marubinotto.util.PasswordDigest; import marubinotto.util.Size; import marubinotto.util.message.CodedException; import net.sf.click.util.ClickUtils; import org.apache.commons.collections.comparators.NullComparator; import org.apache.commons.fileupload.FileItem; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.UnhandledException; public class RawFragment extends RawClassifiable implements Fragment { private String title; private String content; transient public FileItem fileInput; private String fileName; private String fileType; private Size fileSize; transient private List<FragmentRelation> parentRelations; transient private List<FragmentRelation> childRelations; private String password; private boolean asTag = false; private Long tagId; private Tag tag; public RawFragment() { } public RawFragment(User user) { super(user); ensureCanCreate(user); } public Fragment copyForUpdate() { return (Fragment)getDeepCopy(); } @Override public String toString() { return "#" + getId() + (this.title != null ? " " + this.title : ""); } // // Title // public String makeHeadline() { if (getTitle() != null) return getTitle(); if (isFile()) return getFileName(); return makeContentHeadline(); } public String makeHeadline(int maxLength) { String headline = makeHeadline(); if (headline == null) return null; if (headline.length() > maxLength) { headline = headline.substring(0, maxLength); headline = headline + "..."; } return headline; } public String getTitle() { return this.title; } public void setTitle(String title) { this.title = title; } public void setTitleByUser(String title, User user) { Assert.Arg.notNull(user, "user"); if (ObjectUtils.equals(title, this.title) && !canChangeTitle(user)) return; ensureCanChangeTitle(user); if (title != null && title.length() > TITLE_MAX_LENGTH) { throw new CodedException("fragment-title-invalid-max-size", String.valueOf(TITLE_MAX_LENGTH)); } setTitle(title); onPropertyChange(user); } // // Content // public String getContent() { return this.content; } public final static int HEADLINE_MAX_LENGTH = 100; public String makeContentHeadline() { String content = getContent(); if (StringUtils.isBlank(content)) return null; String src = content.trim(); String firstLine = getFirstLine(src).trim(); String firstSentence = getFirstSentence(firstLine); String headline = firstSentence; // Trim to the max length if (headline.length() > HEADLINE_MAX_LENGTH) { headline = headline.substring(0, HEADLINE_MAX_LENGTH); } // trimmed to - the first line <or> the first sentence <or> the max length if (headline.length() < src.length()) { if (headline.equals(firstLine)) headline = headline + " "; headline = headline + "..."; } return headline; } private static String getFirstLine(String text) { try { return new BufferedReader(new StringReader(text)).readLine(); } catch (IOException e) { throw new UnhandledException(e); } } private static String getFirstSentence(String text) { BreakIterator iterator = BreakIterator.getSentenceInstance(); iterator.setText(text); int end = iterator.next(); if (end == BreakIterator.DONE) return text; else return text.substring(0, end); } public boolean hasMoreThanHeadline() { if (isFile()) return true; if (StringUtils.isBlank(getContent())) return false; if (getTitle() != null) return true; // is text && has content && hasn't a title return !getContent().trim().equals(makeContentHeadline()); } public void setContent(String content) { this.content = content; } public void setContentByUser(String content, User user) { Assert.Arg.notNull(user, "user"); if (ObjectUtils.equals(content, this.content) && !canChange(user)) return; ensureCanChange(user); setContent(content); onPropertyChange(user); } public String toStringWithDescendents() { StringBuffer buffer = new StringBuffer(); buffer.append(toString()); Stack<Long> stack = new Stack<Long>(); toStringRecursively(this, buffer, stack); return buffer.toString(); } private static void toStringRecursively( Fragment fragment, StringBuffer buffer, Stack<Long> stack) { if (!fragment.hasChildren()) return; stack.push(fragment.getId()); buffer.append(" ("); boolean first = true; for (Fragment child : fragment.getChildren()) { if (first) first = false; else buffer.append(", "); if (child.getId() == null) throw new IllegalStateException("Missing fragment ID: " + buffer + "?"); if (stack.contains(child.getId())) { buffer.append(child.toString() + " <loop>"); } else { buffer.append(child.toString()); toStringRecursively(child, buffer, stack); } } buffer.append(")"); stack.pop(); } // // Special // public boolean isHome() { return getId() != null ? getId().equals(Fragment.ID_HOME) : false; } // // Classification // public boolean isPublic() { return getClassification().isSubordinateOf(Tag.NAME_PUBLIC); } public boolean isTrash() { return getClassification().isSubordinateOf(Tag.NAME_TRASH); } public boolean isUserFragment() { return getClassification().isSubordinateOf(Tag.NAME_USER); } // // Authorization // public static boolean canCreate(User user) { Assert.Arg.notNull(user, "user"); try { ensureCanCreate(user); return true; } catch (AuthorizationException e) { return false; } } private static void ensureCanCreate(User user) { if (user.isViewer()) { throw new AuthorizationException("no-auth-to-create-fragment"); } } public final boolean canChangeTitle(User user) { Assert.Arg.notNull(user, "user"); try { ensureCanChangeTitle(user); return true; } catch (AuthorizationException e) { return false; } } @Override public void ensureCanChange(User user) throws AuthorizationException { super.ensureCanChange(user); if (user.isViewer()) { throwNoAuthToChangeFragmentError(); } if (isUserFragment()) { if (!user.isOwner() && !user.getName().equals(getTitle())) throwNoAuthToChangeFragmentError(); } } protected void ensureCanChangeTitle(User user) throws AuthorizationException { ensureCanChange(user); if (isHome()) { throwNoAuthToChangeFragmentError(); } if (isUserFragment()) { if (!user.isOwner()) throwNoAuthToChangeFragmentError(); } } @Override protected void ensureCanAddTag(Tag tag, User user) throws AuthorizationException { super.ensureCanAddTag(tag, user); if (tag.isClassifiedAs(Tag.NAME_TRASH) && !canDelete(user)) { throwNoAuthToDeleteFragment(); } } @Override public void ensureCanDelete(User user) throws AuthorizationException { super.ensureCanDelete(user); if (isHome()) { throwNoAuthToDeleteFragment(); } if (isUserFragment()) { if (!user.isOwner()) throwNoAuthToDeleteFragment(); } } private void throwNoAuthToChangeFragmentError() { throw new AuthorizationException( "no-auth-to-change-fragment", ObjectUtils.toString(getId(), "null")); } private void throwNoAuthToDeleteFragment() { throw new AuthorizationException( "no-auth-to-delete-fragment", ObjectUtils.toString(getId(), "null")); } // // File // public void setFileInput(FileItem fileInput) { Assert.Arg.notNull(fileInput, "fileInput"); this.fileInput = fileInput; this.fileName = getFileName(fileInput); this.fileType = getFileType(this.fileName); this.fileSize = new Size(fileInput.getSize()); } public static String getFileType(String fileName) { String extension = FilenameUtils.getExtension(fileName); return StringUtils.isNotBlank(extension) ? extension.toLowerCase() : null; } public FileItem getFileInput() { return this.fileInput; } private static String getFileName(FileItem fileItem) { return FilenameUtils.getName(fileItem.getName()); } public String getFileName() { return this.fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public boolean isFile() { return this.fileName != null; } public String getFileType() { return this.fileType; } public void setFileType(String fileType) { this.fileType = fileType; } public String getMimeType() { if (!isFile()) { return null; } // modification for the mime-type table if (this.fileName.endsWith(".svg")) return "image/svg+xml"; return ClickUtils.getMimeType(this.fileName); } public boolean isImageFile() { if (!isFile()) { return false; } String mimeType = getMimeType(); if (mimeType == null) { return false; } return mimeType.startsWith("image/"); } public Size getFileSize() { return this.fileSize; } public void setFileSize(Size fileSize) { this.fileSize = fileSize; } // // Relations // public void checkTwoWayRelations() { for (FragmentRelation relation : getParentRelations()) relation.twoWay = false; for (FragmentRelation relation : getChildRelations()) relation.twoWay = false; for (FragmentRelation parent : getParentRelations()) { for (FragmentRelation child : getChildRelations()) { if (parent.isSamePairAs(child)) { parent.twoWay = true; child.twoWay = true; } } } } // Parents public List<FragmentRelation> getParentRelations() { if (this.parentRelations == null) { this.parentRelations = new ArrayList<FragmentRelation>(); } return this.parentRelations; } public List<FragmentRelation> navigateToOneWayParents(Long contextRelationId) { List<FragmentRelation> relations = new ArrayList<FragmentRelation>(); for (FragmentRelation relation : getParentRelations()) { // exclude the context relation if (contextRelationId != null && relation.getId().equals(contextRelationId)) continue; // include only one-way parents if (!relation.twoWay) relations.add(relation); } return relations; } public List<Fragment> getParents() { List<Fragment> parents = new ArrayList<Fragment>(); for (FragmentRelation parentRelation : getParentRelations()) parents.add(parentRelation.from); return parents; } public void addParent(Fragment fragment) { Assert.Arg.notNull(fragment, "fragment"); addParentRelation(new FragmentRelation(fragment, this)); } public void addParentRelation(FragmentRelation relation) { Assert.Arg.notNull(relation, "relation"); Assert.Arg.notNull(relation.from, "relation.from"); if (relation.from.getId() != null) Assert.require( !relation.from.getId().equals(getId()), "!relation.from.id.equals(this.id)"); relation.to = this; getParentRelations().add(relation); } public void setParentRelations(List<FragmentRelation> relations) { Assert.Arg.notNull(relations, "relations"); getParentRelations().clear(); for (FragmentRelation parent : relations) { addParentRelation(parent); } } public FragmentRelation getParentRelationByParentId(long parentId) { for (FragmentRelation relation : getParentRelations()) { if (relation.from.getId().longValue() == parentId) { return relation; } } return null; } // Children public boolean hasChildren() { return getChildRelations().size() > 0; } public boolean hasChildren(boolean publicOnly) { return getChildRelations(publicOnly).size() > 0; } public List<FragmentRelation> getChildRelations() { if (this.childRelations == null) { this.childRelations = new ArrayList<FragmentRelation>(); } return this.childRelations; } public List<FragmentRelation> getChildRelations(boolean publicOnly) { if (publicOnly) { List<FragmentRelation> publicChildren = new ArrayList<FragmentRelation>(); for (FragmentRelation relation : getChildRelations()) { if (relation.to.isPublic()) publicChildren.add(relation); } return publicChildren; } else { return getChildRelations(); } } public boolean isNavigableToChildren(FragmentRelation contextRelation) { return navigateToChildren(contextRelation).size() > 0; } public List<FragmentRelation> navigateToChildren(FragmentRelation contextRelation) { if (contextRelation == null) return getChildRelations(); return navigateToChildren(contextRelation.from.getId()); } public List<FragmentRelation> navigateToChildren(Long contextParentId) { if (contextParentId == null) return getChildRelations(); List<FragmentRelation> relations = new ArrayList<FragmentRelation>(); for (FragmentRelation relation : getChildRelations()) { if (!contextParentId.equals(relation.to.getId())) relations.add(relation); } return relations; } public List<Fragment> getChildren() { List<Fragment> children = new ArrayList<Fragment>(); for (FragmentRelation childRelation : getChildRelations()) children.add(childRelation.to); return children; } public FragmentList<RawFragment> getRawChildren() { return new FragmentList<RawFragment>(this).getChildren(); } public void addChild(Fragment fragment) { Assert.Arg.notNull(fragment, "fragment"); addChildRelation(new FragmentRelation(this, fragment)); } public void addChildRelation(FragmentRelation relation) { Assert.Arg.notNull(relation, "relation"); Assert.Arg.notNull(relation.to, "relation.to"); if (relation.to.getId() != null) Assert.require(!relation.to.getId().equals(getId()), "!relation.to.id.equals(this.id)"); relation.from = this; getChildRelations().add(relation); } public void setChildRelations(List<FragmentRelation> childRelations) { Assert.Arg.notNull(childRelations, "childRelations"); getChildRelations().clear(); for (FragmentRelation child : childRelations) { addChildRelation(child); } } public void sortChildRelations() { if (this.childRelations == null) { return; } Collections.sort(this.childRelations, childRelationsComparator); } @SuppressWarnings("rawtypes") private static final Comparator nullLowComparator = new NullComparator(false); public static final Comparator<FragmentRelation> childRelationsComparator = new Comparator<FragmentRelation>() { @SuppressWarnings("unchecked") public int compare(FragmentRelation o1, FragmentRelation o2) { int result = nullLowComparator.compare(o2.priority, o1.priority); if (result != 0) return result; return nullLowComparator.compare(o1.getId(), o2.getId()); } }; // // Password // public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean validatePassword(String password) throws Exception { Assert.Property.requireNotNull(title, "title"); // the default password is the same as the fragment title (user name) if (this.password == null) return this.title.equals(password); PasswordDigest pd = new PasswordDigest(); String encrypted = pd.digestWithStoredSalt(password, this.password); return encrypted.equals(this.password); } public void changePassword(String password) throws Exception { Assert.Arg.notNull(password, "password"); PasswordDigest pd = new PasswordDigest(); this.password = pd.createSshaDigest(password); } // // As a tag // public boolean isTag() { return this.asTag; } private void setAsTag(boolean asTag) { this.asTag = asTag; } public Long getTagId() { return this.tagId; } public void setTagId(Long tagId) { this.tagId = tagId; setAsTag(this.tagId != null); } public Tag asTag() { return this.tag; } public void setTag(Tag tag) { this.tag = tag; } public void setAsTagByUser(boolean asTag, User user) { Assert.Arg.notNull(user, "user"); if (ObjectUtils.equals(asTag, this.asTag) && !canChange(user)) return; ensureCanChange(user); setAsTag(asTag); onPropertyChange(user); } // ensure: asTag() != null if isTag() == true public void validateAsTag(User user, TagRepository tagRepository) throws Exception { Tag tag = asTag(); if (tag == null && getTagId() != null) { tag = tagRepository.get(getTagId()); setTag(tag); } // new or update if (isTag()) { if (StringUtils.isBlank(getTitle())) throw new InvalidTitleException("blank-tag-fragment-title"); if (tag == null) { // new tag = tagRepository.newInstance(getTitle(), user); setTag(tag); } tag.setNameByUser(getTitle(), user); tag.updateTagsByUser(getClassification().getTagNames(), tagRepository, user); tagRepository.validate(tag); } // delete else { if (tag != null) tag.ensureCanDelete(user); } } public void syncWith(Tag tag, User user) throws InvalidTaggingException { Assert.Arg.notNull(tag, "tag"); Assert.Arg.notNull(user, "user"); setTitleByUser(tag.getName(), user); syncClassificationWith(tag); } }