Třídy pro práci se soubory a přílohami záznamů.

refs #131
Verze_2.0
Josef Rokos 10 years ago
parent 566b408ddd
commit 678c140a94

@ -1,15 +1,15 @@
package info.bukova.isspst.data; package info.bukova.isspst.data;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index; import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed; import org.hibernate.search.annotations.Indexed;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity @Entity
@Table(name = "FILE_METAINFO") @Table(name = "FILE_METAINFO")
@Indexed @Indexed
@ -27,6 +27,12 @@ public class FileMetainfo extends BaseData {
@Type(type = "text") @Type(type = "text")
@Field(index = Index.YES, analyze = Analyze.YES) @Field(index = Index.YES, analyze = Analyze.YES)
private String content; private String content;
@Column(name = "MD5")
private String md5;
@Column(name = "DESCRIPTION")
private String description;
@Column(name = "CONTENT_TYPE")
private String contentType;
public String getFileName() { public String getFileName() {
return fileName; return fileName;
@ -68,4 +74,61 @@ public class FileMetainfo extends BaseData {
this.content = content; this.content = content;
} }
public String getMd5() {
return md5;
}
public void setMd5(String md5) {
this.md5 = md5;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FileMetainfo)) return false;
FileMetainfo that = (FileMetainfo) o;
if (getId() != 0 && getId() != that.getId()) return false;
if (recordId != that.recordId) return false;
if (content != null ? !content.equals(that.content) : that.content != null) return false;
if (contentType != null ? !contentType.equals(that.contentType) : that.contentType != null) return false;
if (description != null ? !description.equals(that.description) : that.description != null) return false;
if (fileName != null ? !fileName.equals(that.fileName) : that.fileName != null) return false;
if (md5 != null ? !md5.equals(that.md5) : that.md5 != null) return false;
if (moduleId != null ? !moduleId.equals(that.moduleId) : that.moduleId != null) return false;
if (pathInFilesystem != null ? !pathInFilesystem.equals(that.pathInFilesystem) : that.pathInFilesystem != null)
return false;
return true;
}
@Override
public int hashCode() {
int result = fileName != null ? fileName.hashCode() : 0;
result = 31 * result + (pathInFilesystem != null ? pathInFilesystem.hashCode() : 0);
result = 31 * result + (moduleId != null ? moduleId.hashCode() : 0);
result = 31 * result + recordId;
result = 31 * result + (content != null ? content.hashCode() : 0);
result = 31 * result + (md5 != null ? md5.hashCode() : 0);
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + (contentType != null ? contentType.hashCode() : 0);
return result;
}
} }

@ -1,31 +1,20 @@
package info.bukova.isspst.data; package info.bukova.isspst.data;
import info.bukova.isspst.storage.EntityWithAttachment;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.hibernate.search.annotations.*;
import javax.persistence.*;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
@Entity @Entity
@Table(name = "TRIPREQUIREMENT") @Table(name = "TRIPREQUIREMENT")
@Indexed @Indexed
public class TripRequirement extends RequirementBase { public class TripRequirement extends RequirementBase implements EntityWithAttachment {
@Column(name = "TRIP_FROM") @Column(name = "TRIP_FROM")
@Field(index = Index.YES, analyze = Analyze.YES) @Field(index = Index.YES, analyze = Analyze.YES)
@ -49,7 +38,7 @@ public class TripRequirement extends RequirementBase {
private Boolean requireDownPayment; private Boolean requireDownPayment;
@Column(name = "DOWN_PAYMENT", precision = 15, scale = 4) @Column(name = "DOWN_PAYMENT", precision = 15, scale = 4)
private BigDecimal downPayment; private BigDecimal downPayment;
@OneToMany @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@LazyCollection(LazyCollectionOption.TRUE) @LazyCollection(LazyCollectionOption.TRUE)
@IndexedEmbedded @IndexedEmbedded
private List<FileMetainfo> attachedFiles; private List<FileMetainfo> attachedFiles;
@ -57,6 +46,7 @@ public class TripRequirement extends RequirementBase {
public TripRequirement() { public TripRequirement() {
this.setOwnedBy(new User()); this.setOwnedBy(new User());
passengers = new ArrayList<User>(); passengers = new ArrayList<User>();
attachedFiles = new ArrayList<FileMetainfo>();
} }
public String getFrom() { public String getFrom() {
@ -131,6 +121,7 @@ public class TripRequirement extends RequirementBase {
this.downPayment = downPayment; this.downPayment = downPayment;
} }
@Override
public List<FileMetainfo> getAttachedFiles() { public List<FileMetainfo> getAttachedFiles() {
return attachedFiles; return attachedFiles;
} }
@ -139,4 +130,14 @@ public class TripRequirement extends RequirementBase {
this.attachedFiles = attachedFiles; this.attachedFiles = attachedFiles;
} }
@Override
public void addAttachment(FileMetainfo metaInfo) {
attachedFiles.add(metaInfo);
}
@Override
public void removeAttachment(FileMetainfo metainfo) {
attachedFiles.remove(metainfo);
}
} }

@ -1,21 +1,17 @@
package info.bukova.isspst.services.requirement; package info.bukova.isspst.services.requirement;
import info.bukova.isspst.Constants; import info.bukova.isspst.Constants;
import info.bukova.isspst.data.NumberSeries; import info.bukova.isspst.data.*;
import info.bukova.isspst.data.RequirementState;
import info.bukova.isspst.data.TripBill;
import info.bukova.isspst.data.TripRequirement;
import info.bukova.isspst.data.User;
import info.bukova.isspst.services.LazyLoader; import info.bukova.isspst.services.LazyLoader;
import info.bukova.isspst.services.tripbill.TripBillService; import info.bukova.isspst.services.tripbill.TripBillService;
import info.bukova.isspst.services.workgroups.WorkgroupService; import info.bukova.isspst.services.workgroups.WorkgroupService;
import org.hibernate.Hibernate;
import java.util.Date;
import org.hibernate.LazyInitializationException; import org.hibernate.LazyInitializationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
public class TripRequirementServiceImpl extends RequirementBaseServiceImpl<TripRequirement> public class TripRequirementServiceImpl extends RequirementBaseServiceImpl<TripRequirement>
implements TripRequirementService, RequirementBaseService<TripRequirement> { implements TripRequirementService, RequirementBaseService<TripRequirement> {
@ -53,6 +49,14 @@ public class TripRequirementServiceImpl extends RequirementBaseServiceImpl<TripR
} }
} }
@Transactional
@LazyLoader("form")
public void loadAttachments(TripRequirement entity) {
TripRequirement e = dao.getById(entity.getId());
Hibernate.initialize(e.getAttachedFiles());
entity.setAttachedFiles(e.getAttachedFiles());
}
@Override @Override
protected void postApprove(TripRequirement entity) { protected void postApprove(TripRequirement entity) {
if (entity.getState() == RequirementState.APPROVED) { if (entity.getState() == RequirementState.APPROVED) {

@ -0,0 +1,103 @@
package info.bukova.isspst.storage;
import java.io.*;
/**
* Abstraktni třída pro práci se soubory.
*
* @author Pepa Rokos
*/
public abstract class AbstractFileStorage<T> implements FileStorage<T> {
protected void saveFileDataToPath(byte[] data, String path) {
File file = new File(path);
FileOutputStream os = null;
try {
os = new FileOutputStream(file);
os.write(data);
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.flush();
os.close();
}
} catch (IOException e) {
throw new StorageException("Cannot close stream", e.getCause());
}
}
}
protected void saveFileToPtah(File file, String path) {
File dest = new File(path + File.pathSeparator + file.getName());
FileOutputStream fos = null;
try {
fos = new FileOutputStream(dest);
fos.write(readFile(file));
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
throw new StorageException("Cannot move file: " + file.getName(), e.getCause());
} catch (IOException e) {
throw new StorageException("Cannot move file: " + file.getName(), e.getCause());
} finally {
if (fos != null) {
try {
fos.flush();
fos.close();
} catch (IOException e) {
throw new StorageException("Cannot close stream", e.getCause());
}
}
}
}
protected void removeFileByPath(String path) {
File f = new File(path);
if (!f.delete()) {
throw new StorageException("Cannot delete file: " + path);
}
}
protected byte[] fileDataFromPath(String path) {
File f = new File(path);
return readFile(f);
}
protected byte[] readFile(File file) {
byte[] out = new byte[(int) file.length()];
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
fis.read(out);
fis.close();
} catch (FileNotFoundException e) {
throw new StorageException("File cannot be found: " + file.getName(), e.getCause());
} catch (IOException e) {
throw new StorageException("Cannot read file: " + file.getName(), e.getCause());
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new StorageException("Cannot close stream", e.getCause());
}
}
}
return out;
}
protected File fileFromPath(String path) {
return new File(path);
}
}

@ -0,0 +1,66 @@
package info.bukova.isspst.storage;
import info.bukova.isspst.data.FileMetainfo;
import java.io.File;
/**
* Rozhraní servisního objektu pro práci s přílohami u záznamů. Přílohy jsou reprezentovány objektem FileMetainfo,
* kde je zobrazované jméno souboru, vygenerovaný GUID, pod kterým je soubor fyzicky uložen a MD5 součet používaný
* pro deduplikaci.
*
* @see info.bukova.isspst.data.FileMetainfo
*
* @author Pepa Rokos
*/
public interface DocumentFileStorage extends FileStorage<FileMetainfo> {
/**
* Uloží data předaná jako byte[] do uložiště příloh a vrátí metainformace.
* Metoda řeší deduplikaci souborů, takže v uložišti je fyzicky vždy jen jedna kopie.
*
* @see info.bukova.isspst.data.FileMetainfo
*
* @param data Data souboru
* @param name Zobrazovaný název souboru
* @return Metainformace o souboru
*/
public FileMetainfo saveAndCreateInfo(byte[] data, String name);
/**
* Uloží data předaná jako File do uložiště příloh a vrátí metainformace.
* Metoda řeší deduplikaci souborů, takže v uložišti je fyzicky vždy jen jedna kopie.
*
* @see info.bukova.isspst.data.FileMetainfo
*
* @param file Objekt reprezentující soubor
* @param name Zobrazovaný název souboru
* @return Metainformace o souboru
*/
public FileMetainfo saveAndCreateInfo(File file, String name);
/**
* Vrátí metainformace podle fyzické cesty v uložišti.
*
* @param path Cesta k souboru v uložišti
* @return Metainformace o souboru
*/
public FileMetainfo getMetainfoForPath(String path);
/**
* Bezpečně odstraní přílohu ze záznamu. Záznam o příloze odstraní z kolekce příloh a následně zavolá metodu
* removeFile(FileMetainfo info), která soubor fyzicky smaže, pokud už není nikam linkovaný.
*
* @param entity Entita s přílohami
* @param metaInfo Příloha k odstranění
*/
public void removeAttachment(EntityWithAttachment entity, FileMetainfo metaInfo);
/**
* Bezpečně odstraní všechny přílohy ze záznamu.
*
* @param entity Entita s přílohami
*/
public void removaAllAttachments(EntityWithAttachment entity);
}

@ -0,0 +1,222 @@
package info.bukova.isspst.storage;
import info.bukova.isspst.dao.QueryDao;
import info.bukova.isspst.data.FileMetainfo;
import org.apache.commons.codec.binary.Hex;
import org.hibernate.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.UUID;
/**
* @author Pepa Rokos
*
* Třída pro práci s uložištěm příloh u záznamů. Na soubory příloh se odkazuje pomocí objektů FileMetainfo.
*
* @see info.bukova.isspst.data.FileMetainfo
*/
public class DocumentFileStorageImpl extends AbstractFileStorage<FileMetainfo> implements DocumentFileStorage {
private String rootPath;
@Autowired
private QueryDao queryDao;
/**
* Nastavuje kořenová adresář pro ukládání příloh. Nastavuje se absolutní cesta na filesystému serveru.
*
* @param rootPath kořenový adresář pro přílohy
*/
public void setRootPath(String rootPath) {
this.rootPath = rootPath;
}
@Override
public void saveFile(byte[] data, FileMetainfo fileID) {
String fileName = generateFileName(fileID.getFileName());
saveFileDataToPath(data, rootPath + File.separator + fileName);
fileID.setPathInFilesystem(fileName);
fileID.setContentType(MimeTypes.getContentType(data, fileName));
}
@Override
public void saveFile(File file, FileMetainfo fileID) {
String fileName = generateFileName(fileID.getFileName());
saveFileToPtah(file, rootPath + File.separator + fileName);
fileID.setPathInFilesystem(fileName);
fileID.setContentType(MimeTypes.getContentType(readFile(file), fileName));
}
private String generateFileName(String originalName) {
String extension = null;
String fileName = UUID.randomUUID().toString();
if (originalName != null && !originalName.isEmpty()) {
extension = MimeTypes.fileExtension(originalName);
}
if (extension != null) {
fileName = fileName + "." + extension;
}
return fileName;
}
@Override
@Transactional
public void removeFile(FileMetainfo fileID) {
if (fileID.getPathInFilesystem() == null || fileID.getPathInFilesystem().isEmpty()) {
return;
}
if (infosForPath(fileID.getPathInFilesystem()).size() <= 1) {
removeFileByPath(rootPath + File.separator + fileID.getPathInFilesystem());
}
}
@Override
public void moveFile(String source, String destination) {
throw new UnsupportedOperationException();
}
@Override
public void createDirectory(String dir) {
throw new UnsupportedOperationException();
}
@Override
public byte[] fileData(FileMetainfo fileID) {
if (fileID.getPathInFilesystem() != null && !fileID.getPathInFilesystem().isEmpty()) {
return fileDataFromPath(rootPath + File.separator + fileID.getPathInFilesystem());
}
return null;
}
@Override
public File file(FileMetainfo fileID) {
if (fileID.getPathInFilesystem() != null && !fileID.getPathInFilesystem().isEmpty()) {
return fileFromPath(rootPath + File.separator + fileID.getPathInFilesystem());
}
return null;
}
@Override
public boolean dirExists(String path) {
throw new UnsupportedOperationException();
}
@Override
public String serverPath(FileMetainfo fileID) {
return "/api/dl/" + fileID.getPathInFilesystem() + "/" + fileID.getFileName();
}
@Override
@Transactional
public FileMetainfo saveAndCreateInfo(byte[] data, String name) {
FileMetainfo metaInfo = new FileMetainfo();
metaInfo.setFileName(name);
if (!checkForDuplicate(new ByteArrayInputStream(data), metaInfo)) {
saveFile(data, metaInfo);
}
return metaInfo;
}
@Override
@Transactional
public FileMetainfo saveAndCreateInfo(File file, String name) {
FileMetainfo metaInfo = new FileMetainfo();
metaInfo.setFileName(name);
try {
if (!checkForDuplicate(new FileInputStream(file), metaInfo)) {
saveFile(file, metaInfo);
}
} catch (FileNotFoundException e) {
//TODO: ošetřit
e.printStackTrace();
}
return metaInfo;
}
@Override
@Transactional
public FileMetainfo getMetainfoForPath(String path) {
if (infosForPath(path).isEmpty()) {
return null;
}
return infosForPath(path).get(0);
}
private List<FileMetainfo> infosForPath(String path) {
Query q = queryDao.getQuery("from FileMetainfo info where info.pathInFilesystem = :path");
q.setString("path", path);
return q.list();
}
private boolean checkForDuplicate(InputStream is, FileMetainfo info) {
String md5 = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] dataBytes = new byte[1024];
int nread = 0;
while ((nread = is.read(dataBytes)) != -1) {
md.update(dataBytes, 0, nread);
}
md5 = new String(Hex.encodeHex(md.digest()));
} catch (NoSuchAlgorithmException e) {
//TODO: ošetřit
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
}
Query q = queryDao.getQuery("select info from FileMetainfo info where info.md5 = :md5");
q.setString("md5", md5);
List<FileMetainfo> found = (List<FileMetainfo>) q.list();
if (!found.isEmpty()) {
FileMetainfo foundInfo = found.get(0);
info.setPathInFilesystem(foundInfo.getPathInFilesystem());
info.setMd5(foundInfo.getMd5());
info.setContentType(foundInfo.getContentType());
return true;
} else {
info.setMd5(md5);
return false;
}
}
@Override
@Transactional
public void removeAttachment(EntityWithAttachment entity, FileMetainfo metaInfo) {
entity.removeAttachment(metaInfo);
removeFile(metaInfo);
}
@Override
@Transactional
public void removaAllAttachments(EntityWithAttachment entity) {
for (FileMetainfo metaInfo : entity.getAttachedFiles()) {
removeFile(metaInfo);
}
entity.getAttachedFiles().clear();
}
}

@ -0,0 +1,18 @@
package info.bukova.isspst.storage;
import info.bukova.isspst.data.FileMetainfo;
import java.util.List;
/**
* Rozhraní datových entit s přílohami.
*
* @author Pepa Rokos
*/
public interface EntityWithAttachment {
public List<FileMetainfo> getAttachedFiles();
public void addAttachment(FileMetainfo metaInfo);
public void removeAttachment(FileMetainfo metainfo);
}

@ -2,17 +2,22 @@ package info.bukova.isspst.storage;
import java.io.File; import java.io.File;
public interface FileStorage { /**
* Rozhraní pro uložiště souborů
*
* @param <T> typ objektu, kterým se odkazuje na soubory
*/
public interface FileStorage<T> {
// public String getRootPath(); // public String getRootPath();
public void saveFile(byte[] data, String fileName); public void saveFile(byte[] data, T fileID);
public void saveFile(File file, String path); public void saveFile(File file, T fileId);
public void removeFile(String fileName); public void removeFile(T fileID);
public void moveFile(String source, String destination); public void moveFile(String source, String destination);
public void createDirectory(String dir); public void createDirectory(String dir);
public byte[] fileData(String fileName); public byte[] fileData(T fileID);
public File file(String fileName); public File file(T fileID);
public boolean dirExists(String path); public boolean dirExists(String path);
public String serverPath(String fileName); public String serverPath(T fileID);
} }

@ -1,14 +1,9 @@
package info.bukova.isspst.storage; package info.bukova.isspst.storage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import java.io.*;
public class LocalFileStorage implements FileStorage { public class LocalFileStorage extends AbstractFileStorage<String> {
private String rootPath; private String rootPath;
private ServletContext context; private ServletContext context;
@ -26,96 +21,27 @@ public class LocalFileStorage implements FileStorage {
} }
@Override @Override
public void saveFile(byte[] data, String fileName) { public void saveFile(byte[] data, String fileID) {
saveFileDataToPath(data, getFullPath() + fileID);
File file = new File(getFullPath() + fileName);
FileOutputStream os = null;
try {
os = new FileOutputStream(file);
os.write(data);
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (os != null) {
os.flush();
os.close();
}
} catch (IOException e) {
throw new StorageException("Cannot close stream", e.getCause());
}
}
} }
@Override public void saveFile(File file, String fileID) {
public void saveFile(File file, String path) { saveFileToPtah(file, getFullPath() + fileID);
File dest = new File(getFullPath() + path + File.pathSeparator + file.getName());
FileOutputStream fos = null;
try {
fos = new FileOutputStream(dest);
fos.write(fileData(file.getName()));
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
throw new StorageException("Cannot move file: " + file.getName(), e.getCause());
} catch (IOException e) {
throw new StorageException("Cannot move file: " + file.getName(), e.getCause());
} finally {
if (fos != null) {
try {
fos.flush();
fos.close();
} catch (IOException e) {
throw new StorageException("Cannot close stream", e.getCause());
}
}
}
} }
@Override @Override
public void removeFile(String fileName) { public void removeFile(String fileID) {
File f = new File(getFullPath() + fileName); removeFileByPath(getFullPath() + fileID);
if (!f.delete()) {
throw new StorageException("Cannot delete file: " + getFullPath()
+ fileName);
}
} }
@Override @Override
public byte[] fileData(String fileName) { public byte[] fileData(String fileID) {
File f = new File(getFullPath() + fileName); return fileDataFromPath(getFullPath() + fileID);
byte[] out = new byte[(int) f.length()];
FileInputStream fis = null;
try {
fis = new FileInputStream(f);
fis.read(out);
fis.close();
} catch (FileNotFoundException e) {
throw new StorageException("File cannot be found: " + fileName, e.getCause());
} catch (IOException e) {
throw new StorageException("Cannot read file: " + fileName, e.getCause());
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
throw new StorageException("Cannot close stream", e.getCause());
}
}
}
return out;
} }
@Override @Override
public File file(String fileName) { public File file(String fileID) {
return new File(getFullPath() + fileName); return fileFromPath(getFullPath() + fileID);
} }
@Override @Override
@ -139,8 +65,8 @@ public class LocalFileStorage implements FileStorage {
} }
@Override @Override
public String serverPath(String fileName) { public String serverPath(String fileID) {
return context.getRealPath(rootPath + File.separator + fileName); return context.getRealPath(rootPath + File.separator + fileID);
} }
} }

@ -0,0 +1,284 @@
package info.bukova.isspst.storage;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
/**
* Created by pepa on 30.1.15.
*/
public class MimeTypes {
public static String getContentType(byte[] data)
{
return getContentType(data, null);
}
public static String getContentType(byte[] data, String name)
{
if (data == null)
{
return null;
}
byte[] header = new byte[11];
System.arraycopy(data, 0, header, 0, Math.min(data.length, header.length));
int c1 = header[0] & 0xff;
int c2 = header[1] & 0xff;
int c3 = header[2] & 0xff;
int c4 = header[3] & 0xff;
int c5 = header[4] & 0xff;
int c6 = header[5] & 0xff;
int c7 = header[6] & 0xff;
int c8 = header[7] & 0xff;
int c9 = header[8] & 0xff;
int c10 = header[9] & 0xff;
int c11 = header[10] & 0xff;
if (c1 == 0xCA && c2 == 0xFE && c3 == 0xBA && c4 == 0xBE)
{
return "application/java-vm";
}
if (c1 == 0xD0 && c2 == 0xCF && c3 == 0x11 && c4 == 0xE0 && c5 == 0xA1 && c6 == 0xB1 && c7 == 0x1A && c8 == 0xE1)
{
// if the name is set then check if it can be validated by name, because it could be a xls or powerpoint
String contentType = guessContentTypeFromName(name);
if (contentType != null)
{
return contentType;
}
return "application/msword";
}
if (c1 == 0x25 && c2 == 0x50 && c3 == 0x44 && c4 == 0x46 && c5 == 0x2d && c6 == 0x31 && c7 == 0x2e)
{
return "application/pdf";
}
if (c1 == 0x38 && c2 == 0x42 && c3 == 0x50 && c4 == 0x53 && c5 == 0x00 && c6 == 0x01)
{
return "image/photoshop";
}
if (c1 == 0x25 && c2 == 0x21 && c3 == 0x50 && c4 == 0x53)
{
return "application/postscript";
}
if (c1 == 0xff && c2 == 0xfb && c3 == 0x30)
{
return "audio/mp3";
}
if (c1 == 0x49 && c2 == 0x44 && c3 == 0x33)
{
return "audio/mp3";
}
if (c1 == 0xAC && c2 == 0xED)
{
// next two bytes are version number, currently 0x00 0x05
return "application/x-java-serialized-object";
}
if (c1 == '<')
{
if (c2 == '!' ||
((c2 == 'h' && (c3 == 't' && c4 == 'm' && c5 == 'l' || c3 == 'e' && c4 == 'a' && c5 == 'd') || (c2 == 'b' && c3 == 'o' && c4 == 'd' && c5 == 'y'))) ||
((c2 == 'H' && (c3 == 'T' && c4 == 'M' && c5 == 'L' || c3 == 'E' && c4 == 'A' && c5 == 'D') || (c2 == 'B' && c3 == 'O' && c4 == 'D' && c5 == 'Y'))))
{
return "text/html";
}
if (c2 == '?' && c3 == 'x' && c4 == 'm' && c5 == 'l' && c6 == ' ')
{
return "application/xml";
}
}
// big and little endian UTF-16 encodings, with byte order mark
if (c1 == 0xfe && c2 == 0xff)
{
if (c3 == 0 && c4 == '<' && c5 == 0 && c6 == '?' && c7 == 0 && c8 == 'x')
{
return "application/xml";
}
}
if (c1 == 0xff && c2 == 0xfe)
{
if (c3 == '<' && c4 == 0 && c5 == '?' && c6 == 0 && c7 == 'x' && c8 == 0)
{
return "application/xml";
}
}
if (c1 == 'B' && c2 == 'M')
{
return "image/bmp";
}
if (c1 == 0x49 && c2 == 0x49 && c3 == 0x2a && c4 == 0x00)
{
return "image/tiff";
}
if (c1 == 0x4D && c2 == 0x4D && c3 == 0x00 && c4 == 0x2a)
{
return "image/tiff";
}
if (c1 == 'G' && c2 == 'I' && c3 == 'F' && c4 == '8')
{
return "image/gif";
}
if (c1 == '#' && c2 == 'd' && c3 == 'e' && c4 == 'f')
{
return "image/x-bitmap";
}
if (c1 == '!' && c2 == ' ' && c3 == 'X' && c4 == 'P' && c5 == 'M' && c6 == '2')
{
return "image/x-pixmap";
}
if (c1 == 137 && c2 == 80 && c3 == 78 && c4 == 71 && c5 == 13 && c6 == 10 && c7 == 26 && c8 == 10)
{
return "image/png";
}
if (c1 == 0xFF && c2 == 0xD8 && c3 == 0xFF)
{
if (c4 == 0xE0)
{
return "image/jpeg";
}
/**
* File format used by digital cameras to store images. Exif Format can be read by any application supporting JPEG. Exif Spec can be found at:
* http://www.pima.net/standards/it10/PIMA15740/Exif_2-1.PDF
*/
if ((c4 == 0xE1) && (c7 == 'E' && c8 == 'x' && c9 == 'i' && c10 == 'f' && c11 == 0))
{
return "image/jpeg";
}
if (c4 == 0xEE)
{
return "image/jpg";
}
}
/**
* According to http://www.opendesign.com/files/guestdownloads/OpenDesign_Specification_for_.dwg_files.pdf
* first 6 bytes are of type "AC1018" (for example) and the next 5 bytes are 0x00.
*/
if ((c1 == 0x41 && c2 == 0x43) && (c7 == 0x00 && c8 == 0x00 && c9 == 0x00 && c10 == 0x00 && c11 == 0x00))
{
return "application/acad";
}
if (c1 == 0x2E && c2 == 0x73 && c3 == 0x6E && c4 == 0x64)
{
return "audio/basic"; // .au
// format,
// big
// endian
}
if (c1 == 0x64 && c2 == 0x6E && c3 == 0x73 && c4 == 0x2E)
{
return "audio/basic"; // .au
// format,
// little
// endian
}
if (c1 == 'R' && c2 == 'I' && c3 == 'F' && c4 == 'F')
{
/*
* I don't know if this is official but evidence suggests that .wav files start with "RIFF" - brown
*/
return "audio/x-wav";
}
if (c1 == 'P' && c2 == 'K')
{
// its application/zip but this could be a open office thing if name is given
String contentType = guessContentTypeFromName(name);
if (contentType != null)
{
return contentType;
}
return "application/zip";
}
return guessContentTypeFromName(name);
}
private static final Map<String, String> mimeTypes = new HashMap<String, String>();
public static String guessContentTypeFromName(String name)
{
if (name == null) return null;
int lastIndex = name.lastIndexOf('.');
if (lastIndex != -1)
{
String extention = name.substring(lastIndex + 1).toLowerCase();
if (mimeTypes.size() == 0)
{
HashMap<String, String> tempMap = new HashMap<String, String>();
InputStream is = MimeTypes.class.getResourceAsStream("mime.types.properties");
try
{
Properties properties = new Properties();
properties.load(is);
for (Object key : properties.keySet())
{
String property = properties.getProperty((String)key);
StringTokenizer st = new StringTokenizer(property, " ");
while (st.hasMoreTokens())
{
tempMap.put(st.nextToken(), (String)key);
}
}
}
catch (IOException e)
{
//Debug.error(e);
}
finally
{
try
{
is.close();
}
catch (IOException e)
{
//Debug.error(e);
}
}
synchronized (mimeTypes)
{
mimeTypes.putAll(tempMap);
}
}
return mimeTypes.get(extention);
}
return null;
}
public static String fileExtension(String fileName) {
int index = fileName.lastIndexOf(".");
if (index > -1) {
return fileName.substring(index + 1);
}
return "";
}
}

@ -0,0 +1,66 @@
package info.bukova.isspst.storage;
import info.bukova.isspst.data.FileMetainfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Created by pepa on 2.2.15.
*/
@Controller
public class StorageController {
@Autowired
private DocumentFileStorage documentStorage;
private static final Logger logger = LoggerFactory.getLogger(StorageController.class);
@RequestMapping(value = "dl/{path}/{name}")
public void viewFile(HttpServletResponse response, @PathVariable("path") String path,
@PathVariable("name") String name) {
ServletOutputStream os = null;
try {
os = response.getOutputStream();
FileMetainfo metainfo = new FileMetainfo();
metainfo.setPathInFilesystem(path);
byte[] data = null;
try {
data = documentStorage.fileData(metainfo);
metainfo.setContentType(MimeTypes.getContentType(data, path));
} catch (StorageException e) {
response.sendError(404);
}
if (data != null) {
response.setContentType(metainfo.getContentType());
response.setContentLength(data.length);
os.write(data, 0, data.length);
os.flush();
os.close();
}
} catch (IOException e) {
logger.error("I/O error: " + e.getMessage());
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

@ -1,24 +1,21 @@
package info.bukova.isspst.ui.requirement; package info.bukova.isspst.ui.requirement;
import java.util.List; import info.bukova.isspst.data.*;
import org.zkoss.bind.annotation.BindingParam;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.Init;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.zk.ui.select.annotation.WireVariable;
import info.bukova.isspst.data.SettingsData;
import info.bukova.isspst.data.TripRequirement;
import info.bukova.isspst.data.User;
import info.bukova.isspst.data.Workgroup;
import info.bukova.isspst.services.requirement.RequirementTypeService; import info.bukova.isspst.services.requirement.RequirementTypeService;
import info.bukova.isspst.services.requirement.TripRequirementService; import info.bukova.isspst.services.requirement.TripRequirementService;
import info.bukova.isspst.services.settings.GlobalSettingsService; import info.bukova.isspst.services.settings.GlobalSettingsService;
import info.bukova.isspst.services.users.UserService; import info.bukova.isspst.services.users.UserService;
import info.bukova.isspst.services.workgroups.WorkgroupService; import info.bukova.isspst.services.workgroups.WorkgroupService;
import info.bukova.isspst.storage.DocumentFileStorage;
import info.bukova.isspst.ui.FormViewModel; import info.bukova.isspst.ui.FormViewModel;
import info.bukova.isspst.validators.TripRequirementFormValidator; import info.bukova.isspst.validators.TripRequirementFormValidator;
import org.zkoss.bind.annotation.*;
import org.zkoss.zk.ui.event.UploadEvent;
import org.zkoss.zk.ui.select.annotation.WireVariable;
import org.zkoss.zul.Filedownload;
import java.util.ArrayList;
import java.util.List;
public class TripRequirementForm extends FormViewModel<TripRequirement> { public class TripRequirementForm extends FormViewModel<TripRequirement> {
@ -32,9 +29,13 @@ public class TripRequirementForm extends FormViewModel<TripRequirement> {
private RequirementTypeService reqTypeService; private RequirementTypeService reqTypeService;
@WireVariable @WireVariable
private TripRequirementService tripRequirementService; private TripRequirementService tripRequirementService;
@WireVariable
private DocumentFileStorage documentStorage;
private List<Workgroup> centres; private List<Workgroup> centres;
private List<User> users; private List<User> users;
private List<User> passengers; private List<User> passengers;
private List<FileMetainfo> attachments;
private List<FileMetainfo> forDelete;
private User selUser; private User selUser;
private TripRequirementFormValidator validator; private TripRequirementFormValidator validator;
@ -44,6 +45,8 @@ public class TripRequirementForm extends FormViewModel<TripRequirement> {
centres = reqTypeService.filterCentres(getDataBean().getType(), workgroupService.getUserCentres(userService.getCurrent())); centres = reqTypeService.filterCentres(getDataBean().getType(), workgroupService.getUserCentres(userService.getCurrent()));
users = userService.getUsersForCombo(); users = userService.getUsersForCombo();
passengers = getDataBean().getPassengers(); passengers = getDataBean().getPassengers();
attachments = getDataBean().getAttachedFiles();
forDelete = new ArrayList<FileMetainfo>(); // kolekce příloh na smazání v případě uložení záznamu
validator = new TripRequirementFormValidator(); validator = new TripRequirementFormValidator();
} }
@ -87,4 +90,37 @@ public class TripRequirementForm extends FormViewModel<TripRequirement> {
return passengers; return passengers;
} }
@Command
@NotifyChange("attachments")
public void uploadAttachment(@ContextParam(ContextType.TRIGGER_EVENT) UploadEvent upEvent) {
FileMetainfo metaInfo = documentStorage.saveAndCreateInfo(upEvent.getMedia().getByteData(),
upEvent.getMedia().getName());
getDataBean().addAttachment(metaInfo);
}
@Command
@NotifyChange("attachments")
public void deleteAttachment(@BindingParam("attachment") FileMetainfo metaInfo) {
getDataBean().removeAttachment(metaInfo);
forDelete.add(metaInfo); // smazat až v případě uložení záznamu
}
@Command
public void downloadAttachment(@BindingParam("attachment") FileMetainfo metaInfo) {
Filedownload.save(documentStorage.fileData(metaInfo), metaInfo.getContentType(), metaInfo.getFileName());
}
public List<FileMetainfo> getAttachments() {
return attachments;
}
@Override
protected void doSave() {
for (FileMetainfo info : forDelete) {
documentStorage.removeFile(info);
}
super.doSave();
}
} }

@ -7,10 +7,6 @@ import info.bukova.isspst.services.requirement.TripRequirementService;
import info.bukova.isspst.services.users.UserService; import info.bukova.isspst.services.users.UserService;
import info.bukova.isspst.services.workgroups.WorkgroupService; import info.bukova.isspst.services.workgroups.WorkgroupService;
import info.bukova.isspst.ui.ListViewModel; import info.bukova.isspst.ui.ListViewModel;
import java.util.ArrayList;
import java.util.List;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
import org.zkoss.bind.BindUtils; import org.zkoss.bind.BindUtils;
import org.zkoss.bind.annotation.GlobalCommand; import org.zkoss.bind.annotation.GlobalCommand;
@ -18,6 +14,9 @@ import org.zkoss.bind.annotation.Init;
import org.zkoss.bind.annotation.NotifyChange; import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.zk.ui.select.annotation.WireVariable; import org.zkoss.zk.ui.select.annotation.WireVariable;
import java.util.ArrayList;
import java.util.List;
public class TripRequirementList extends ListViewModel<TripRequirement> { public class TripRequirementList extends ListViewModel<TripRequirement> {
@WireVariable @WireVariable
@ -79,11 +78,6 @@ public class TripRequirementList extends ListViewModel<TripRequirement> {
return myCentres; return myCentres;
} }
@Override
protected void loadLazyDataForEdit(TripRequirement data) {
tripRequirementService.loadPassangers(data);
}
@Override @Override
protected List<TripRequirement> getListFromService() protected List<TripRequirement> getListFromService()
{ {

@ -29,6 +29,7 @@
<value>/WEB-INF/mail.properties</value> <value>/WEB-INF/mail.properties</value>
<value>/WEB-INF/gmail.properties</value> <value>/WEB-INF/gmail.properties</value>
<value>/WEB-INF/ad.properties</value> <value>/WEB-INF/ad.properties</value>
<value>/WEB-INF/storage.properties</value>
</list> </list>
</property> </property>
</bean> </bean>
@ -52,10 +53,10 @@
<property name="hibernateProperties"> <property name="hibernateProperties">
<props> <props>
<prop key="hibernate.dialect">${jdbc.dialect}</prop> <prop key="hibernate.dialect">${jdbc.dialect}</prop>
<prop key="hibernate.show_sql">true</prop> <prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop> <prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.search.default.directory_provider">filesystem</prop> <prop key="hibernate.search.default.directory_provider">filesystem</prop>
<prop key="hibernate.search.default.indexBase">./</prop> <prop key="hibernate.search.default.indexBase">${storage.fulltextIndex}</prop>
<prop key="hibernate.search.analyzer">org.apache.lucene.analysis.cz.CzechAnalyzer</prop> <prop key="hibernate.search.analyzer">org.apache.lucene.analysis.cz.CzechAnalyzer</prop>
<!-- <prop key="hibernate.enable_lazy_load_no_trans">true</prop> --> <!-- <prop key="hibernate.enable_lazy_load_no_trans">true</prop> -->
</props> </props>
@ -177,6 +178,10 @@
<property name="rootPath" value="/WEB-INF/upload"/> <property name="rootPath" value="/WEB-INF/upload"/>
</bean> </bean>
<bean id="documentStorage" class="info.bukova.isspst.storage.DocumentFileStorageImpl">
<property name="rootPath" value="${storage.root}"/>
</bean>
<!-- Session data --> <!-- Session data -->
<bean id="sessionData" class="info.bukova.isspst.SessionData" scope="session"> <bean id="sessionData" class="info.bukova.isspst.SessionData" scope="session">
<aop:scoped-proxy/> <aop:scoped-proxy/>

@ -0,0 +1,2 @@
storage.root=/home/pepa/tmp/isspst
storage.fulltextIndex=/home/pepa/tmp/isspst

@ -1,57 +1,62 @@
<?page title="${labels.AgendaSearch}" contentType="text/html;charset=UTF-8"?> <?page title="${labels.AgendaSearch}" contentType="text/html;charset=UTF-8"?>
<zk> <zk xmlns="http://www.zkoss.org/2005/zul"
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.zkoss.org/2005/zul http://www.zkoss.org/2005/zul/zul.xsd">
<window border="normal" closable="false" <?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('info.bukova.isspst.ui.search.SearchForm')"
vflex="1">
<caption src="/img/search-032.png" zclass="form-caption" label="${labels.AgendaSearch}" />
<vbox width="100%" <window border="normal" closable="false"
vflex="1"> apply="org.zkoss.bind.BindComposer"
<hbox hflex="1" align="center"> viewModel="@id('vm') @init('info.bukova.isspst.ui.search.SearchForm')"
<div align="center" hflex="1"> vflex="1">
<vbox> <caption src="/img/search-032.png" zclass="form-caption" label="${labels.AgendaSearch}"/>
<image src="/img/lucene_logo.png"/>
<hbox>
<textbox value="@bind(vm.keyWord)" instant="true" width="250px" onOK="@command('doSearch')"/>
<button label="${labels.Search}" onClick="@command('doSearch')" sclass="nicebutton" image="/img/search.png" />
</hbox>
</vbox>
</div>
</hbox>
<panel hflex="1" vflex="1">
<panelchildren style="overflow:auto;">
<vbox children="@load(vm.results)" width="100%">
<template name="children">
<vbox width="100%">
<separator bar="true" width="100%"/>
<label value="@load(each.recordName)" style="font-weight: bold; font-size:14px;"/>
<label value="@load(each.description)"/>
<hbox>
<label value="${labels.created}: "/>
<label value="@load(each.created)"/>
</hbox>
<hbox>
<label value="${labels.ownedBy}: "/>
<label value="@load(each.ownedBy)"/>
</hbox>
<hbox>
<label value="${labels.modified}: "/>
<label value="@load(each.modified)"/>
</hbox>
<hbox>
<label value="${labels.modifiedBy}: "/>
<label value="@load(each.modifiedBy)"/>
</hbox>
<a href="@load(each.url)" label="@load(each.url)"/>
</vbox>
</template>
</vbox>
</panelchildren>
</panel>
</vbox>
</window> <vbox width="100%"
vflex="1">
<hbox hflex="1" align="center">
<div align="center" hflex="1">
<vbox>
<image src="/img/lucene_logo.png"/>
<hbox>
<textbox value="@bind(vm.keyWord)" instant="true" width="250px"
onOK="@command('doSearch')"/>
<button label="${labels.Search}" onClick="@command('doSearch')" sclass="nicebutton"
image="/img/search.png"/>
</hbox>
</vbox>
</div>
</hbox>
<panel hflex="1" vflex="1">
<panelchildren style="overflow:auto;">
<vbox children="@load(vm.results)" width="100%">
<template name="children">
<vbox width="100%">
<separator bar="true" width="100%"/>
<label value="@load(each.recordName)" style="font-weight: bold; font-size:14px;"/>
<label value="@load(each.description)"/>
<hbox>
<label value="${labels.created}: "/>
<label value="@load(each.created)"/>
</hbox>
<hbox>
<label value="${labels.ownedBy}: "/>
<label value="@load(each.ownedBy)"/>
</hbox>
<hbox>
<label value="${labels.modified}: "/>
<label value="@load(each.modified)"/>
</hbox>
<hbox>
<label value="${labels.modifiedBy}: "/>
<label value="@load(each.modifiedBy)"/>
</hbox>
<a href="@load(each.url)" label="@load(each.url)"/>
</vbox>
</template>
</vbox>
</panelchildren>
</panel>
</vbox>
</window>
</zk> </zk>

@ -1,5 +1,8 @@
<?page title="${labels.RequirementsFormTitle}" contentType="text/html;charset=UTF-8"?> <?page title="${labels.RequirementsFormTitle}" contentType="text/html;charset=UTF-8"?>
<zk> <zk xmlns="http://www.zkoss.org/2005/zul"
xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.zkoss.org/2005/zul http://www.zkoss.org/2005/zul/zul.xsd">
<window <window
id="editWin" id="editWin"
closable="true" closable="true"
@ -12,118 +15,144 @@
zclass="form-caption" zclass="form-caption"
label="${labels.RequirementsFormTitle}" /> label="${labels.RequirementsFormTitle}" />
<vlayout form="@id('fx') @load(vm.dataBean) @save(vm.dataBean, before='save') @validator(vm.validator)"> <vlayout form="@id('fx') @load(vm.dataBean) @save(vm.dataBean, before='save') @validator(vm.validator)">
<grid hflex="min"> <hbox>
<columns> <grid hflex="min">
<column <columns>
align="right" <column
hflex="min" /> align="right"
<column /> hflex="min" />
</columns> <column />
<rows> </columns>
<row> <rows>
<cell sclass="row-title">${labels.RequirementsFormNumberSerie} :</cell> <row>
<cell> <cell sclass="row-title">${labels.RequirementsFormNumberSerie} :</cell>
<textbox <cell>
id="numser" <textbox
width="200px" id="numser"
value="@bind(fx.numser)" width="200px"
maxlength="@load(vm.lengthText)" value="@bind(fx.numser)"
readonly="true" /> maxlength="@load(vm.lengthText)"
</cell> readonly="true" />
</row> </cell>
<row> </row>
<cell sclass="row-title">${labels.RequirementsFormReqDate} :</cell> <row>
<cell> <cell sclass="row-title">${labels.RequirementsFormReqDate} :</cell>
<datebox <cell>
id="reqDate" <datebox
width="200px" id="reqDate"
value="@bind(fx.reqDate)" width="200px"
format="${labels.DateFormat}" /> value="@bind(fx.reqDate)"
</cell> format="${labels.DateFormat}" />
</row> </cell>
<row> </row>
<cell sclass="row-title">${labels.RequirementsFormCenter} :</cell> <row>
<cell> <cell sclass="row-title">${labels.RequirementsFormCenter} :</cell>
<combobox <cell>
model="@load(vm.centres)" <combobox
readonly="true" model="@load(vm.centres)"
selectedItem="@bind(fx.centre)"> readonly="true"
<template name="model"> selectedItem="@bind(fx.centre)">
<comboitem label="@load(each.fullName)" /> <template name="model">
</template> <comboitem label="@load(each.fullName)" />
</combobox> </template>
</cell> </combobox>
</row> </cell>
<row> </row>
<cell sclass="row-title">${labels.RequirementsFormFrom} :</cell> <row>
<cell> <cell sclass="row-title">${labels.RequirementsFormFrom} :</cell>
<textbox <cell>
id="from" <textbox
width="300px" id="from"
maxlength="@load(vm.lengthText)" width="300px"
value="@bind(fx.from)" /> maxlength="@load(vm.lengthText)"
</cell> value="@bind(fx.from)" />
</row> </cell>
<row> </row>
<cell sclass="row-title">${labels.RequirementsFormStartDateTime} :</cell> <row>
<cell> <cell sclass="row-title">${labels.RequirementsFormStartDateTime} :</cell>
<hbox> <cell>
<datebox <hbox>
id="tripDate" <datebox
width="200px" id="tripDate"
format="medium" width="200px"
value="@bind(fx.tripDate)" /> format="medium"
<timebox value="@bind(fx.tripDate)" />
id="tripTime" <timebox
width="90px" id="tripTime"
format="short" width="90px"
value="@bind(fx.tripDate)" /> format="short"
value="@bind(fx.tripDate)" />
</hbox> </hbox>
</cell> </cell>
</row> </row>
<row> <row>
<cell sclass="row-title">${labels.RequirementsFormTo} :</cell> <cell sclass="row-title">${labels.RequirementsFormTo} :</cell>
<cell> <cell>
<textbox <textbox
id="to" id="to"
width="300px" width="300px"
maxlength="@load(vm.lengthText)" maxlength="@load(vm.lengthText)"
value="@bind(fx.to)" /> value="@bind(fx.to)" />
</cell> </cell>
</row> </row>
<row> <row>
<cell sclass="row-title">${labels.RequirementsFormPurpose} :</cell> <cell sclass="row-title">${labels.RequirementsFormPurpose} :</cell>
<cell> <cell>
<textbox <textbox
id="description" id="description"
width="300px" width="300px"
maxlength="@load(vm.lengthText)" maxlength="@load(vm.lengthText)"
value="@bind(fx.description)" /> value="@bind(fx.description)" />
</cell> </cell>
</row> </row>
<row> <row>
<cell sclass="row-title">${labels.RequirementsFormEndTravel} :</cell> <cell sclass="row-title">${labels.RequirementsFormEndTravel} :</cell>
<cell> <cell>
<textbox <textbox
id="end" id="end"
width="200px" width="200px"
maxlength="@load(vm.lengthText)" maxlength="@load(vm.lengthText)"
value="@bind(fx.end)" /> value="@bind(fx.end)" />
</cell> </cell>
</row> </row>
<row> <row>
<cell sclass="row-title">${labels.RequirementsFormEndDate} :</cell> <cell sclass="row-title">${labels.RequirementsFormEndDate} :</cell>
<cell> <cell>
<datebox <datebox
id="endDate" id="endDate"
width="300px" width="300px"
format="medium" format="medium"
value="@bind(fx.endDate)" /> value="@bind(fx.endDate)" />
</cell> </cell>
</row> </row>
</rows> </rows>
</grid> </grid>
<vbox>
<button label="Připojit"
upload="true"
onUpload="@command('uploadAttachment')"
sclass="nicebutton" />
<grid model="@load(vm.attachments)">
<columns>
<column/>
</columns>
<rows>
<template name="model">
<row>
<a href="@load('/api/dl/'.concat(each.pathInFilesystem).concat('/').concat(each.fileName))"
label="@load(each.fileName)"
target="blank"/>
<hbox>
<button label="Odebrat" onClick="@command('deleteAttachment', attachment=each)"/>
<button label="Stáhnout" onClick="@command('downloadAttachment', attachment=each)"/>
</hbox>
</row>
</template>
</rows>
</grid>
</vbox>
</hbox>
<vbox> <vbox>
<label value="${labels.RequirementsFormPassengers}"/> <label value="${labels.RequirementsFormPassengers}"/>
<hbox> <hbox>
@ -147,7 +176,7 @@
<rows> <rows>
<template name="model"> <template name="model">
<row> <row>
<label value="@load(each)"/> <label value="@load(each)" />
<button label="${labels.RemoveItem}" <button label="${labels.RemoveItem}"
onClick="@command('removePassanger', user=each)" onClick="@command('removePassanger', user=each)"
sclass="nicebutton"/> sclass="nicebutton"/>

Loading…
Cancel
Save