basic contact overview
This commit is contained in:
		| @@ -1,50 +0,0 @@ | ||||
| package de.jooqfaces; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| public enum EJooqFacesApplicationScope { | ||||
| 	/** | ||||
| 	 * jooqFacesUrl | ||||
| 	 */ | ||||
| 	JOOQ_FACES_URL("jooqFacesUrl"), | ||||
| 	/** | ||||
| 	 * jooqFacesDriver | ||||
| 	 */ | ||||
| 	JOOQ_FACES_DRIVER("jooqFacesDriver"), | ||||
| 	/** | ||||
| 	 * jooqFacesSqldialect | ||||
| 	 */ | ||||
| 	JOOQ_FACES_SQLDIALECT("jooqFacesSqldialect"),  | ||||
| 	/** | ||||
| 	 * jooqFacesProperties | ||||
| 	 */ | ||||
| 	JOOQ_FACES_PROPERTIES("jooqFacesProperties"),  | ||||
| 	/** | ||||
| 	 * jooqFacesConnectionPool | ||||
| 	 */ | ||||
| 	JOOQ_FACES_CONNECTIONPOOL("jooqFacesConnectionPool"), | ||||
| 	/** | ||||
| 	 * jooqFacesMaxPoolSize | ||||
| 	 */ | ||||
| 	JOOQ_FACES_MAXPOOLSIZE("jooqFacesMaxPoolSize"),  | ||||
| 	/** | ||||
| 	 * jooqFacesParamAutocommit | ||||
| 	 */ | ||||
| 	JOOQ_FACES_PARAM_AUTOCOMMIT("jooqFacesParamAutocommit"); | ||||
| 	 | ||||
| 	private final String s; | ||||
|  | ||||
| 	private EJooqFacesApplicationScope(String s) { | ||||
| 		this.s = s; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * @return the value | ||||
| 	 */ | ||||
| 	public final String get() { | ||||
| 		return s; | ||||
| 	} | ||||
| } | ||||
| @@ -1,24 +0,0 @@ | ||||
| package de.jooqfaces; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  * | ||||
|  */ | ||||
| public enum EJooqFacesConnectionPool { | ||||
| 	CP_HIKARI("hikari"); | ||||
|  | ||||
| 	private final String value; | ||||
| 	 | ||||
| 	private EJooqFacesConnectionPool(String value) { | ||||
| 		this.value = value; | ||||
| 	} | ||||
| 	 | ||||
| 	public String get() { | ||||
| 		return value; | ||||
| 	} | ||||
| 	 | ||||
| 	public static final String getHikari() { | ||||
| 		return CP_HIKARI.get(); | ||||
| 	} | ||||
| } | ||||
| @@ -1,20 +0,0 @@ | ||||
| package de.jooqfaces; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| public enum EJooqFacesSessionScope { | ||||
| 	CONNECTION("connection"); | ||||
|  | ||||
| 	private final String value; | ||||
|  | ||||
| 	private EJooqFacesSessionScope(String value) { | ||||
| 		this.value = value; | ||||
| 	} | ||||
|  | ||||
| 	public String get() { | ||||
| 		return value; | ||||
| 	} | ||||
| } | ||||
| @@ -1,350 +0,0 @@ | ||||
| package de.jooqfaces; | ||||
|  | ||||
| import java.sql.*; | ||||
| import java.util.*; | ||||
|  | ||||
| import javax.faces.application.*; | ||||
| import javax.faces.application.FacesMessage.*; | ||||
| import javax.faces.component.*; | ||||
| import javax.faces.context.*; | ||||
| import javax.faces.render.*; | ||||
| import javax.servlet.*; | ||||
| import javax.sql.*; | ||||
|  | ||||
| import org.apache.logging.log4j.*; | ||||
| import org.jooq.*; | ||||
| import org.jooq.impl.*; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  * | ||||
|  */ | ||||
| public class JooqFacesContext extends FacesContext { | ||||
| 	private static final Logger LOGGER = LogManager.getLogger(JooqFacesContext.class); | ||||
| 	private FacesContext facesContext; | ||||
| 	private Connection connection; | ||||
|  | ||||
| 	public JooqFacesContext(FacesContext facesContext) { | ||||
| 		this.facesContext = facesContext; | ||||
| 		setCurrentInstance(this); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get the jooq dsl context from the faces context session map<br /> | ||||
| 	 * <br /> | ||||
| 	 * <b>Always</b> call getJooq() within a <b>try-catch closure</b>, as the DSLContext is a closure; if not, your | ||||
| 	 * connections might run out | ||||
| 	 *  | ||||
| 	 * @return the jooq context | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 *           on driver errors; check if you have attached the correct jdbc driver class | ||||
| 	 * @throws SQLException | ||||
| 	 *           on sql errors | ||||
| 	 */ | ||||
| 	public CloseableDSLContext getJooq() throws ClassNotFoundException, SQLException { | ||||
| 		ExternalContext externalContext = facesContext.getExternalContext(); | ||||
| 		if (externalContext == null) { | ||||
| 			throw new JooqFacesException("external context of current faces context is null"); | ||||
| 		} | ||||
| 		ServletContext servletContext = (ServletContext) externalContext.getContext(); | ||||
| 		if (servletContext == null) { | ||||
| 			throw new JooqFacesException("servlet context of current faces context is null"); | ||||
| 		} | ||||
| 		SQLDialect dialect = getSqlDialect(servletContext); | ||||
| 		createConnectionIfNull(externalContext, servletContext); | ||||
| 		return new DefaultCloseableDSLContext(new DefaultConnectionProvider(connection), dialect); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get the database connection from the session map; if not found, create a new one and add it to the session map | ||||
| 	 *  | ||||
| 	 * @param sessionMap | ||||
| 	 *          the session map | ||||
| 	 * @param externalContext | ||||
| 	 *          the external context | ||||
| 	 * @param servletContext | ||||
| 	 *          the servlet context | ||||
| 	 * @return the connection | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 *           on driver errors (e.g. missing jdbc lib) | ||||
| 	 * @throws SQLException | ||||
| 	 *           on sql errors | ||||
| 	 */ | ||||
| 	private void createConnectionIfNull(ExternalContext externalContext, ServletContext servletContext) | ||||
| 			throws ClassNotFoundException, SQLException { | ||||
| 		if (connection == null) { // caching the connection within the faces context makes it faster on the jsf life cycle | ||||
| 			Map<String, Object> sessionMap = externalContext.getSessionMap(); | ||||
| 			if (sessionMap == null) { | ||||
| 				throw new JooqFacesException("session map of current faces context is null"); | ||||
| 			} | ||||
| 			DataSource dataSource = (DataSource) sessionMap.get(EJooqFacesSessionScope.CONNECTION.get()); | ||||
| 			if (dataSource == null || dataSource.getConnection() == null || dataSource.getConnection().isClosed()) { | ||||
| 				LOGGER.debug("creating new connection pool"); | ||||
| 				dataSource = getDataSourceFromServletContext(servletContext); | ||||
| 				externalContext.getSessionMap().put(EJooqFacesSessionScope.CONNECTION.get(), dataSource); | ||||
| 			} | ||||
| 			connection = dataSource.getConnection(); | ||||
| 			String autoCommit = servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_PARAM_AUTOCOMMIT.get()); | ||||
| 			connection.setAutoCommit("true".equals(autoCommit)); // default false for postgreSQL, the database of my choice | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get data source from connection pool if defined in servlet context (see | ||||
| 	 * EJooqFacesApplicationScope.CONNECTION_POOL); if not defined, return a plain data source | ||||
| 	 *  | ||||
| 	 * @param servletContext | ||||
| 	 * @return | ||||
| 	 */ | ||||
| 	private static final DataSource getDataSourceFromServletContext(ServletContext servletContext) | ||||
| 			throws ClassNotFoundException { | ||||
| 		String driver = getDriver(servletContext); | ||||
| 		if (driver == null) { | ||||
| 			throw new JooqFacesException( | ||||
| 					"undefined driver in application scope, define it in your web.xml's context-param on name " | ||||
| 							+ EJooqFacesApplicationScope.JOOQ_FACES_DRIVER.get()); | ||||
| 		} | ||||
| 		String url = getUrl(servletContext); | ||||
| 		if (url == null) { | ||||
| 			throw new JooqFacesException( | ||||
| 					"undefined connection data url in application scope, define it in your web.xml's context-param on name " | ||||
| 							+ EJooqFacesApplicationScope.JOOQ_FACES_URL.get()); | ||||
| 		} | ||||
| 		Integer maxPoolSize = getMaxPoolSize(servletContext); | ||||
| 		if (maxPoolSize == null) { | ||||
| 			LOGGER.debug("maxPoolSize not set, setting it to 20"); | ||||
| 			maxPoolSize = 20; | ||||
| 		} | ||||
| 		String connectionPool = getConnectionPool(servletContext); | ||||
| 		if (connectionPool == null) { | ||||
| 			LOGGER.warn( | ||||
| 					"no connection pool set in servlet context (see EJooqFacesApplicationScope.JOOQ_FACES_CONNECTIONPOOL), using plain connection"); | ||||
| 		} | ||||
| 		return new PoollessDataSource(driver, url); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get the connection from the servlet context | ||||
| 	 *  | ||||
| 	 * @param servletContext | ||||
| 	 *          the servlet context | ||||
| 	 * @return the connection | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 *           on driver errors (e.g. missing jdbc lib) | ||||
| 	 * @throws SQLException | ||||
| 	 *           on sql errors | ||||
| 	 */ | ||||
| 	private static final Connection getConnectionFromServletContext(ServletContext servletContext) | ||||
| 			throws ClassNotFoundException, SQLException { | ||||
| 		DataSource dataSource = getDataSourceFromServletContext(servletContext); | ||||
| 		return dataSource.getConnection(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get a jooq connection from servlet context (for such cases as the deployment phase where the faces context is still | ||||
| 	 * not available) | ||||
| 	 *  | ||||
| 	 * @param servletContext | ||||
| 	 *          the servlet context | ||||
| 	 * @return a jooq connection | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 *           on driver errors (e.g. missing jdbc lib) | ||||
| 	 * @throws SQLException | ||||
| 	 *           on sql errors | ||||
| 	 */ | ||||
| 	public static final DSLContext getJooqFromServletContext(ServletContext servletContext) | ||||
| 			throws ClassNotFoundException, SQLException { | ||||
| 		SQLDialect dialect = getSqlDialect(servletContext); | ||||
| 		Connection con = getConnectionFromServletContext(servletContext); | ||||
| 		return DSL.using(con, dialect); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void addMessage(String clientId, FacesMessage message) { | ||||
| 		facesContext.addMessage(clientId, message); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Application getApplication() { | ||||
| 		return facesContext.getApplication(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Iterator<String> getClientIdsWithMessages() { | ||||
| 		return facesContext.getClientIdsWithMessages(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public ExternalContext getExternalContext() { | ||||
| 		return facesContext.getExternalContext(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Severity getMaximumSeverity() { | ||||
| 		return facesContext.getMaximumSeverity(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Iterator<FacesMessage> getMessages() { | ||||
| 		return facesContext.getMessages(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Iterator<FacesMessage> getMessages(String clientId) { | ||||
| 		return facesContext.getMessages(clientId); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public RenderKit getRenderKit() { | ||||
| 		return facesContext.getRenderKit(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean getRenderResponse() { | ||||
| 		return facesContext.getRenderResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean getResponseComplete() { | ||||
|  | ||||
| 		return facesContext.getResponseComplete(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public ResponseStream getResponseStream() { | ||||
| 		return facesContext.getResponseStream(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public ResponseWriter getResponseWriter() { | ||||
| 		return facesContext.getResponseWriter(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public UIViewRoot getViewRoot() { | ||||
| 		return facesContext.getViewRoot(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void release() { | ||||
| 		facesContext.release(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void renderResponse() { | ||||
| 		facesContext.renderResponse(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void responseComplete() { | ||||
| 		facesContext.responseComplete(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void setResponseStream(ResponseStream responseStream) { | ||||
| 		facesContext.setResponseStream(responseStream); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void setResponseWriter(ResponseWriter responseWriter) { | ||||
| 		facesContext.setResponseWriter(responseWriter); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void setViewRoot(UIViewRoot root) { | ||||
| 		facesContext.setViewRoot(root); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get the connection pool from initial context | ||||
| 	 *  | ||||
| 	 * @param servletContext | ||||
| 	 *          the context | ||||
| 	 * @return the connection pool string or null | ||||
| 	 */ | ||||
| 	private static final String getConnectionPool(ServletContext servletContext) { | ||||
| 		return servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_CONNECTIONPOOL.get()); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get the max pool size from initial context if any | ||||
| 	 *  | ||||
| 	 * @param servletContext | ||||
| 	 *          the context of this function call | ||||
| 	 * @return the max pool size or null | ||||
| 	 */ | ||||
| 	private static final Integer getMaxPoolSize(ServletContext servletContext) { | ||||
| 		String maxPoolSize = servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_MAXPOOLSIZE.get()); | ||||
| 		return maxPoolSize == null ? null : Integer.valueOf(maxPoolSize); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get driver from initial context | ||||
| 	 *  | ||||
| 	 * @param servletContext | ||||
| 	 *          the context of this function call | ||||
| 	 * @return the parameter value of the jooq faces driver | ||||
| 	 */ | ||||
| 	private static final String getDriver(ServletContext servletContext) { | ||||
| 		return servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_DRIVER.get()); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get driver connection url from initial context | ||||
| 	 *  | ||||
| 	 * @param servletContext | ||||
| 	 *          the context of this function call | ||||
| 	 * @return the parameter value of the jooq faces url | ||||
| 	 */ | ||||
| 	private static final String getUrl(ServletContext servletContext) { | ||||
| 		return servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_URL.get()); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * find jooq sql dialect class for dialectName | ||||
| 	 *  | ||||
| 	 * @param dialectName | ||||
| 	 *          name of dialect | ||||
| 	 * @return SQLDialect if found, null otherwise | ||||
| 	 */ | ||||
| 	private static final SQLDialect findDialect(String dialectName) { | ||||
| 		if (dialectName == null) { | ||||
| 			LOGGER.error("Sql dialect name is null"); | ||||
| 			return null; | ||||
| 		} else { | ||||
| 			for (SQLDialect dialect : SQLDialect.values()) { | ||||
| 				LOGGER.trace("Sql dialect comparing: dialectName={}, loopDialect={}", dialectName, dialect); | ||||
| 				if (dialectName.equalsIgnoreCase(dialect.name())) { | ||||
| 					LOGGER.debug("Sql dialect found: dialectName={}, foundDialect={}", dialectName, dialect); | ||||
| 					return dialect; | ||||
| 				} | ||||
| 			} | ||||
| 			LOGGER.error("Sql dialect not found: dialectName={}", dialectName); | ||||
| 			return null; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get jooq sql dialect from initial context | ||||
| 	 *  | ||||
| 	 * @param servletContext | ||||
| 	 *          the context of this function call | ||||
| 	 * @return the dialect or null | ||||
| 	 */ | ||||
| 	private static final SQLDialect getSqlDialect(ServletContext servletContext) { | ||||
| 		String dialectName = servletContext.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_SQLDIALECT.get()); | ||||
| 		return getSqlDialect(dialectName); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get sql dialect from name | ||||
| 	 *  | ||||
| 	 * @param name | ||||
| 	 *          the dialect name | ||||
| 	 * @return the dialect or null | ||||
| 	 */ | ||||
| 	public static final SQLDialect getSqlDialect(String name) { | ||||
| 		return findDialect(name); | ||||
| 	} | ||||
| } | ||||
| @@ -1,26 +0,0 @@ | ||||
| package de.jooqfaces; | ||||
|  | ||||
| import javax.faces.*; | ||||
| import javax.faces.context.*; | ||||
| import javax.faces.lifecycle.*; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  * | ||||
|  */ | ||||
| public class JooqFacesContextFactory extends FacesContextFactory { | ||||
|  | ||||
| 	private FacesContextFactory facesContextFactory; | ||||
|  | ||||
| 	public JooqFacesContextFactory(FacesContextFactory facesContextFactory) { | ||||
| 		this.facesContextFactory = facesContextFactory; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public FacesContext getFacesContext(Object context, Object request, Object response, Lifecycle lifecycle) | ||||
| 			throws FacesException { | ||||
| 		FacesContext facesContext = facesContextFactory.getFacesContext(context, request, response, lifecycle); | ||||
| 		return new JooqFacesContext(facesContext); | ||||
| 	} | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| package de.jooqfaces; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  * | ||||
|  */ | ||||
| public class JooqFacesException extends RuntimeException { | ||||
| 	private static final long serialVersionUID = 1L; | ||||
|  | ||||
| 	public JooqFacesException(String message) { | ||||
| 		super(message); | ||||
| 	} | ||||
|  | ||||
| 	public JooqFacesException(Exception e) { | ||||
| 		super(e); | ||||
| 	} | ||||
| } | ||||
| @@ -1,72 +0,0 @@ | ||||
| package de.jooqfaces; | ||||
|  | ||||
| import java.io.*; | ||||
| import java.sql.*; | ||||
| import java.util.logging.*; | ||||
|  | ||||
| import javax.sql.*; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  * | ||||
|  */ | ||||
| public class PoollessDataSource implements DataSource { | ||||
|  | ||||
| 	private final String driver; | ||||
| 	private final String url; | ||||
|  | ||||
| 	public PoollessDataSource(String driver, String url) { | ||||
| 		this.driver = driver; | ||||
| 		this.url = url; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public PrintWriter getLogWriter() throws SQLException { | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public int getLoginTimeout() throws SQLException { | ||||
| 		return 0; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Logger getParentLogger() throws SQLFeatureNotSupportedException { | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void setLogWriter(PrintWriter out) throws SQLException { | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void setLoginTimeout(int seconds) throws SQLException { | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public boolean isWrapperFor(Class<?> iface) throws SQLException { | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public <T> T unwrap(Class<T> iface) throws SQLException { | ||||
| 		return null; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Connection getConnection() throws SQLException { | ||||
| 		try { | ||||
| 			Class.forName(driver); | ||||
| 		} catch (ClassNotFoundException e) { | ||||
| 			throw new SQLException(e); | ||||
| 		} | ||||
| 		return DriverManager.getConnection(url); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Connection getConnection(String username, String password) throws SQLException { | ||||
| 		return null; | ||||
| 	} | ||||
| } | ||||
| @@ -1,90 +0,0 @@ | ||||
| package de.jooqfaces; | ||||
|  | ||||
| import java.io.*; | ||||
| import java.sql.*; | ||||
| import java.util.*;import javax.servlet.*; | ||||
|  | ||||
| import org.apache.logging.log4j.*; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| public class PropertiesDeploymentListener implements ServletContextListener { | ||||
| 	 | ||||
| 	private static final Logger LOGGER = LogManager.getLogger(PropertiesDeploymentListener.class); | ||||
| 	 | ||||
| 	@Override | ||||
| 	public void contextDestroyed(ServletContextEvent event) { | ||||
| 		try { | ||||
| 			Enumeration<Driver> drivers = DriverManager.getDrivers(); | ||||
| 			while (drivers.hasMoreElements()) { | ||||
| 				DriverManager.deregisterDriver(drivers.nextElement()); | ||||
| 			} | ||||
| 		} catch (SQLException | SecurityException e) { | ||||
| 			LOGGER.error("Error deregistering drivers", e); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void contextInitialized(ServletContextEvent event) { | ||||
| 		try { | ||||
| 			ServletContext ctx = event.getServletContext(); | ||||
| 			beforeInitialization(ctx); | ||||
| 			String propertiesFileName = (String) ctx.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_PROPERTIES.get()); | ||||
| 			if (propertiesFileName == null) { | ||||
| 				throw new IOException( | ||||
| 						"undefined properties file name in application scope, define it in your web.xml's context-param on name " | ||||
| 								+ EJooqFacesApplicationScope.JOOQ_FACES_PROPERTIES.get()); | ||||
| 			} | ||||
| 			Properties properties = new Properties(); | ||||
| 			properties.load(new FileInputStream(propertiesFileName)); | ||||
| 			for (Map.Entry<Object, Object> entry : properties.entrySet()) { | ||||
| 				String key = (String) entry.getKey(); | ||||
| 				String value = (String) entry.getValue(); | ||||
| 				ctx.setInitParameter(key, value); | ||||
| 			} | ||||
| 			// ensure to have all needed parameters loaded | ||||
| 			if (ctx.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_SQLDIALECT.get()) == null) { | ||||
| 				throw new IOException("no " + EJooqFacesApplicationScope.JOOQ_FACES_SQLDIALECT.get() | ||||
| 						+ " defined in your properties file " + propertiesFileName); | ||||
| 			} | ||||
| 			if (ctx.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_URL.get()) == null) { | ||||
| 				throw new IOException("no " + EJooqFacesApplicationScope.JOOQ_FACES_URL.get() + " defined in your properties file " | ||||
| 						+ propertiesFileName); | ||||
| 			} | ||||
| 			if (ctx.getInitParameter(EJooqFacesApplicationScope.JOOQ_FACES_DRIVER.get()) == null) { | ||||
| 				throw new IOException("no " + EJooqFacesApplicationScope.JOOQ_FACES_DRIVER.get() | ||||
| 						+ " defined in your properties file " + propertiesFileName); | ||||
| 			} | ||||
| 			afterInitialization(ctx); | ||||
| 		} catch (IOException e) { | ||||
| 			LOGGER.error("Error loading needed parameters from properties file", e); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * executed directly after initialization if no exception is thrown | ||||
| 	 *  | ||||
| 	 * @param ctx | ||||
| 	 *          the context to use | ||||
| 	 * @throws IOException | ||||
| 	 *           for input output exceptions | ||||
| 	 */ | ||||
| 	public void afterInitialization(ServletContext ctx) throws IOException { | ||||
| 		// to be implemented in extending classes | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * executed directly before initialization after getting the context from the servlet | ||||
| 	 *  | ||||
| 	 * @param ctx | ||||
| 	 *          the context to use | ||||
| 	 * @throws IOException | ||||
| 	 *           for input output exceptions | ||||
| 	 */ | ||||
| 	public void beforeInitialization(ServletContext ctx) throws IOException { | ||||
| 		// to be implemented in extending classes | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,15 @@ | ||||
| package de.jottyfan.timetrack; | ||||
|  | ||||
| import org.springframework.boot.SpringApplication; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.transaction.annotation.EnableTransactionManagement; | ||||
|  | ||||
| @SpringBootApplication | ||||
| @EnableTransactionManagement | ||||
| public class TimetrackApplication { | ||||
|  | ||||
| 	public static void main(String[] args) { | ||||
| 		SpringApplication.run(TimetrackApplication.class, args); | ||||
| 	} | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,41 @@ | ||||
| package de.jottyfan.timetrack.config; | ||||
|  | ||||
| import javax.sql.DataSource; | ||||
|  | ||||
| import org.jooq.SQLDialect; | ||||
| import org.jooq.impl.DataSourceConnectionProvider; | ||||
| import org.jooq.impl.DefaultConfiguration; | ||||
| import org.jooq.impl.DefaultDSLContext; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| @Configuration | ||||
| public class InitialConfiguration { | ||||
| 	@Autowired | ||||
| 	private DataSource dataSource; | ||||
|  | ||||
| 	@Bean | ||||
| 	public DataSourceConnectionProvider connectionProvider() { | ||||
| 		return new DataSourceConnectionProvider(new TransactionAwareDataSourceProxy(dataSource)); | ||||
| 	} | ||||
| 	 | ||||
| 	@Bean | ||||
| 	public DefaultDSLContext dsl() { | ||||
| 		return new DefaultDSLContext(configuration()); | ||||
| 	} | ||||
| 	 | ||||
| 	public DefaultConfiguration configuration() { | ||||
| 		DefaultConfiguration jooqConfiguration = new DefaultConfiguration(); | ||||
| 		jooqConfiguration.set(connectionProvider()); | ||||
| 		jooqConfiguration.set(SQLDialect.POSTGRES); | ||||
| //		jooqConfiguration.set(new DefaultExecuteListenerProvider(exceptionTransformer())); | ||||
| 		return jooqConfiguration; | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,56 @@ | ||||
| package de.jottyfan.timetrack.config; | ||||
|  | ||||
| import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; | ||||
| import org.keycloak.adapters.springsecurity.KeycloakConfiguration; | ||||
| import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; | ||||
| import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; | ||||
| import org.keycloak.adapters.springsecurity.management.HttpSessionManager; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.ComponentScan; | ||||
| import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; | ||||
| import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; | ||||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||||
| import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; | ||||
| import org.springframework.security.core.session.SessionRegistryImpl; | ||||
| import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; | ||||
| import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; | ||||
|  | ||||
| @KeycloakConfiguration | ||||
| @ComponentScan(basePackageClasses = KeycloakSpringBootConfigResolver.class) | ||||
| @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) | ||||
| public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { | ||||
|  | ||||
| 	@Autowired | ||||
| 	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { | ||||
| 		KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider(); | ||||
| 		keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); | ||||
| 		auth.authenticationProvider(keycloakAuthenticationProvider); | ||||
| 	} | ||||
|  | ||||
| 	@Bean | ||||
| 	public KeycloakSpringBootConfigResolver KeycloakConfigResolver() { | ||||
| 		return new KeycloakSpringBootConfigResolver(); | ||||
| 	} | ||||
|  | ||||
| 	@Bean | ||||
| 	@Override | ||||
| 	@ConditionalOnMissingBean(HttpSessionManager.class) | ||||
| 	protected HttpSessionManager httpSessionManager() { | ||||
| 		return new HttpSessionManager(); | ||||
| 	} | ||||
|  | ||||
| 	@Bean | ||||
| 	@Override | ||||
| 	protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { | ||||
| 		return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	protected void configure(HttpSecurity http) throws Exception { | ||||
| 		super.configure(http); | ||||
| 		http.authorizeRequests().antMatchers("/public/**").permitAll(); | ||||
| 		http.csrf().disable(); | ||||
| 	} | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| package de.jottyfan.timetrack.help; | ||||
|  | ||||
| public enum Application | ||||
| { | ||||
| 	CONNECTION("connection"); | ||||
|  | ||||
| 	private final String value; | ||||
|  | ||||
| 	private Application(String value) | ||||
| 	{ | ||||
| 		this.value = value; | ||||
| 	} | ||||
|  | ||||
| 	public String get() | ||||
| 	{ | ||||
| 		return value; | ||||
| 	} | ||||
| } | ||||
| @@ -1,37 +0,0 @@ | ||||
| package de.jottyfan.timetrack.help; | ||||
|  | ||||
| import java.io.*; | ||||
|  | ||||
| import javax.servlet.*; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  *  | ||||
|  */ | ||||
| public class EncodingFilter implements Filter | ||||
| { | ||||
| 	private String encoding; | ||||
|  | ||||
| 	@Override | ||||
| 	public void destroy() | ||||
| 	{ | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException | ||||
| 	{ | ||||
| 		servletRequest.setCharacterEncoding(encoding); | ||||
| 		filterChain.doFilter(servletRequest, servletResponse); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public void init(FilterConfig filterConfig) throws ServletException | ||||
| 	{ | ||||
| 		encoding = filterConfig.getInitParameter("encoding"); | ||||
| 		if (encoding == null) | ||||
| 		{ | ||||
| 			encoding = "UTF-8"; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,31 +0,0 @@ | ||||
| package de.jottyfan.timetrack.help; | ||||
|  | ||||
| import java.time.LocalDate; | ||||
| import java.time.format.DateTimeFormatter; | ||||
|  | ||||
| import javax.faces.component.UIComponent; | ||||
| import javax.faces.context.FacesContext; | ||||
| import javax.faces.convert.Converter; | ||||
| import javax.faces.convert.ConverterException; | ||||
| import javax.faces.convert.FacesConverter; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  * | ||||
|  */ | ||||
| @FacesConverter("de.jottyfan.timetrack.help.LocalDateConverter") | ||||
| public class LocalDateConverter implements Converter<LocalDate> { | ||||
|  | ||||
| 	protected final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd.MM.yyyy"); | ||||
| 	 | ||||
| 	@Override | ||||
| 	public LocalDate getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException { | ||||
| 		return LocalDate.from(dtf.parse(value)); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public String getAsString(FacesContext context, UIComponent component, LocalDate value) throws ConverterException { | ||||
| 		return value.format(dtf); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										32
									
								
								src/main/java/de/jottyfan/timetrack/help/ManifestBean.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/main/java/de/jottyfan/timetrack/help/ManifestBean.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| package de.jottyfan.timetrack.help; | ||||
|  | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.boot.info.BuildProperties; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| @Component | ||||
| public class ManifestBean { | ||||
|  | ||||
| 	@Value("${server.servlet.context-path:}") | ||||
| 	private String contextPath; | ||||
|  | ||||
| 	@Autowired(required = false) | ||||
| 	private BuildProperties buildProperties; | ||||
|  | ||||
| 	public String getVersion() { | ||||
| 		StringBuilder buf = new StringBuilder(); | ||||
| 		buf.append("Version "); | ||||
| 		buf.append(buildProperties == null ? "?" : buildProperties.getVersion()); | ||||
| 		return buf.toString(); | ||||
| 	} | ||||
|  | ||||
| 	public String getContextPath() { | ||||
| 		return (contextPath != null && !contextPath.isBlank()) ? String.format("/%s/", contextPath) : "/"; | ||||
| 	} | ||||
| } | ||||
| @@ -1,20 +0,0 @@ | ||||
| package de.jottyfan.timetrack.help; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| public abstract class Navigation | ||||
| { | ||||
| 	/** | ||||
| 	 * navigate to page referenced by p | ||||
| 	 *  | ||||
| 	 * @param p | ||||
| 	 * @return | ||||
| 	 */ | ||||
| 	public String navigateTo(Pages p) | ||||
| 	{ | ||||
| 		return p.get(); | ||||
| 	} | ||||
| } | ||||
| @@ -1,94 +0,0 @@ | ||||
| package de.jottyfan.timetrack.help; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| public enum Pages | ||||
| { | ||||
| 	/** | ||||
| 	 * contact list | ||||
| 	 */ | ||||
| 	CONTACT_LIST("/pages/contact/list.jsf"), | ||||
| 	/** | ||||
| 	 * contact item | ||||
| 	 */ | ||||
| 	CONTACT_ITEM("/pages/contact/item.jsf"), | ||||
| 	/** | ||||
| 	 * note list | ||||
| 	 */ | ||||
| 	NOTE_LIST("/pages/note/list.jsf"), | ||||
| 	/** | ||||
| 	 * note item | ||||
| 	 */ | ||||
| 	NOTE_ITEM("/pages/note/item.jsf"), | ||||
| 	/** | ||||
| 	 * todo list | ||||
| 	 */ | ||||
| 	TODO_LIST("/pages/todo/list.jsf"),  | ||||
| 	/** | ||||
| 	 * todo item | ||||
| 	 */ | ||||
| 	TODO_ITEM("/pages/todo/item.jsf"), | ||||
| 	/** | ||||
| 	 * done init | ||||
| 	 */ | ||||
| 	DONE_INIT("/pages/done/init.jsf"), | ||||
| 	/** | ||||
| 	 * done edit | ||||
| 	 */ | ||||
| 	DONE_EDIT("/pages/done/edit.jsf"), | ||||
| 	/** | ||||
| 	 * done delete | ||||
| 	 */ | ||||
| 	DONE_DELETE("/pages/done/delete.jsf"), | ||||
| 	/** | ||||
| 	 * done add | ||||
| 	 */ | ||||
| 	DONE_ADD("/pages/done/add.jsf"), | ||||
| 	/** | ||||
| 	 * done clear | ||||
| 	 */ | ||||
| 	DONE_CLEAR("/pages/done/clear.jsf"), | ||||
| 	/** | ||||
| 	 * done read | ||||
| 	 */ | ||||
| 	DONE_READ("/pages/done/read.jsf"), | ||||
| 	/** | ||||
| 	 * organize init | ||||
| 	 */ | ||||
| 	ORGANIZE_LIST("/pages/organize/list.jsf"), | ||||
| 	/** | ||||
| 	 * organize week | ||||
| 	 */ | ||||
| 	ORGANIZE_WEEK("/pages/organize/week.jsf"), | ||||
| 	/** | ||||
| 	 * organize delete | ||||
| 	 */ | ||||
| 	ORGANIZE_DELETE("/pages/organize/delete.jsf"),  | ||||
| 	/** | ||||
| 	 * organize add | ||||
| 	 */ | ||||
| 	ORGANIZE_ADD("/pages/organize/add.jsf"), | ||||
| 	/** | ||||
| 	 * organize edit | ||||
| 	 */ | ||||
| 	ORGANIZE_EDIT("/pages/organize/edit.jsf"), | ||||
| 	/** | ||||
| 	 * start | ||||
| 	 */ | ||||
| 	START("/pages/start.jsf"); | ||||
|  | ||||
| 	private final String value; | ||||
|  | ||||
| 	private Pages(String value) | ||||
| 	{ | ||||
| 		this.value = value; | ||||
| 	} | ||||
|  | ||||
| 	public String get() | ||||
| 	{ | ||||
| 		return value; | ||||
| 	} | ||||
| } | ||||
| @@ -1,61 +0,0 @@ | ||||
| package de.jottyfan.timetrack.help; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.enterprise.context.SessionScoped; | ||||
| import javax.inject.Named; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| @Named | ||||
| @SessionScoped | ||||
| public class ThemeBean implements Serializable | ||||
| { | ||||
| 	private static final long serialVersionUID = 1L; | ||||
| 	 | ||||
| 	private String currentTheme; | ||||
|  | ||||
| 	public List<String> getValidThemes(){ | ||||
| 		List<String> list = new ArrayList<>(); | ||||
| 		list.add("cerulean"); | ||||
| 		list.add("cosmo"); | ||||
| 		list.add("cyborg"); | ||||
| 		list.add("darkly"); | ||||
| 		list.add("default"); | ||||
| 		list.add("flatly"); | ||||
| 		list.add("journal"); | ||||
| 		list.add("lumen"); | ||||
| 		list.add("other"); | ||||
| 		list.add("paper"); | ||||
| 		list.add("patternfly"); | ||||
| 		list.add("readable"); | ||||
| 		list.add("sandstone"); | ||||
| 		list.add("simplex"); | ||||
| 		list.add("slate"); | ||||
| 		list.add("spacelab"); | ||||
| 		list.add("superhero"); | ||||
| 		list.add("united"); | ||||
| 		list.add("yeti"); | ||||
| 		return list; | ||||
| 	} | ||||
| 	 | ||||
| 	public void setCurrentTheme(String currentTheme) | ||||
| 	{ | ||||
| 		this.currentTheme = currentTheme; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * contains current theme for web.xml's theme chooser | ||||
| 	 *  | ||||
| 	 * @return current theme | ||||
| 	 */ | ||||
| 	public String getCurrentTheme() | ||||
| 	{ | ||||
| 		return currentTheme == null ? "default" : (getValidThemes().contains(currentTheme) ? currentTheme : "default");  | ||||
| 	} | ||||
| } | ||||
| @@ -1,66 +0,0 @@ | ||||
| package de.jottyfan.timetrack.modules.contact; | ||||
|  | ||||
| import java.io.Serializable; | ||||
|  | ||||
| import javax.enterprise.context.RequestScoped; | ||||
| import javax.faces.context.FacesContext; | ||||
| import javax.inject.Inject; | ||||
| import javax.inject.Named; | ||||
|  | ||||
| import de.jooqfaces.JooqFacesContext; | ||||
| import de.jottyfan.timetrack.help.Navigation; | ||||
| import de.jottyfan.timetrack.help.Pages; | ||||
| import de.jottyfan.timetrack.modules.ControlInterface; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  *  | ||||
|  */ | ||||
| @Named | ||||
| @RequestScoped | ||||
| public class ContactControl extends Navigation implements ControlInterface, Serializable { | ||||
| 	private static final long serialVersionUID = 1L; | ||||
|  | ||||
| 	@Inject | ||||
| 	@Named("contactModel") | ||||
| 	private ContactModel model; | ||||
|  | ||||
| 	public String toStart() { | ||||
| 		return navigateTo(Pages.START); | ||||
| 	} | ||||
|  | ||||
| 	public String toList() { | ||||
| 		boolean ready = model.init((JooqFacesContext) FacesContext.getCurrentInstance()); | ||||
| 		return ready ? navigateTo(Pages.CONTACT_LIST) : toStart(); | ||||
| 	} | ||||
|  | ||||
| 	public String toItem(ContactBean bean) { | ||||
| 		model.setBean(bean); | ||||
| 		return navigateTo(Pages.CONTACT_ITEM); | ||||
| 	} | ||||
|  | ||||
| 	public String toItem() { | ||||
| 		model.setBean(new ContactBean(null)); | ||||
| 		return navigateTo(Pages.CONTACT_ITEM); | ||||
| 	} | ||||
|  | ||||
| 	public String doAdd() { | ||||
| 		boolean ready = model.add((JooqFacesContext) FacesContext.getCurrentInstance()); | ||||
| 		return ready ? toList() : navigateTo(Pages.CONTACT_ITEM); | ||||
| 	} | ||||
|  | ||||
| 	public String doUpdate() { | ||||
| 		boolean ready = model.update((JooqFacesContext) FacesContext.getCurrentInstance()); | ||||
| 		return ready ? toList() : navigateTo(Pages.CONTACT_ITEM); | ||||
| 	} | ||||
|  | ||||
| 	public String doDelete() { | ||||
| 		boolean ready = model.delete((JooqFacesContext) FacesContext.getCurrentInstance()); | ||||
| 		return ready ? toList() : navigateTo(Pages.CONTACT_ITEM); | ||||
| 	} | ||||
| 	 | ||||
| 	public Integer getAmount() { | ||||
| 		return model.getAmount((JooqFacesContext) FacesContext.getCurrentInstance()); | ||||
| 	} | ||||
| } | ||||
| @@ -1,176 +0,0 @@ | ||||
| package de.jottyfan.timetrack.modules.contact; | ||||
|  | ||||
| import static de.jottyfan.timetrack.db.contact.Tables.T_CONTACT; | ||||
|  | ||||
| import java.sql.SQLException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.jooq.CloseableDSLContext; | ||||
| import org.jooq.DeleteConditionStep; | ||||
| import org.jooq.InsertValuesStep4; | ||||
| import org.jooq.Record1; | ||||
| import org.jooq.Record5; | ||||
| import org.jooq.SelectJoinStep; | ||||
| import org.jooq.UpdateConditionStep; | ||||
| import org.jooq.exception.DataAccessException; | ||||
| import org.jooq.impl.DSL; | ||||
|  | ||||
| import de.jooqfaces.JooqFacesContext; | ||||
| import de.jottyfan.timetrack.db.contact.enums.EnumContacttype; | ||||
| import de.jottyfan.timetrack.db.contact.tables.records.TContactRecord; | ||||
| import de.jottyfan.timetrack.modules.JooqGateway; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  * | ||||
|  */ | ||||
| public class ContactGateway extends JooqGateway { | ||||
| 	private static final Logger LOGGER = LogManager.getLogger(ContactGateway.class); | ||||
|  | ||||
| 	public ContactGateway(JooqFacesContext facesContext) { | ||||
| 		super(facesContext); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get sorted list of contacts | ||||
| 	 *  | ||||
| 	 * @return a list (an empty one at least) | ||||
| 	 * @throws SQLException | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 * @throws DataAccessException | ||||
| 	 */ | ||||
| 	public List<ContactBean> getAll() throws DataAccessException, ClassNotFoundException, SQLException { | ||||
| 		try (CloseableDSLContext jooq = getJooq()) { | ||||
| 			SelectJoinStep<Record5<Integer, String, String, String, EnumContacttype>> sql = jooq | ||||
| 			// @formatter:off | ||||
| 				.select(T_CONTACT.PK, | ||||
| 						    T_CONTACT.FORENAME, | ||||
| 						    T_CONTACT.SURNAME, | ||||
| 						    T_CONTACT.CONTACT, | ||||
| 						    T_CONTACT.TYPE) | ||||
| 				.from(T_CONTACT); | ||||
| 			// @formatter:on | ||||
| 			LOGGER.debug("{}", sql.toString()); | ||||
| 			List<ContactBean> list = new ArrayList<>(); | ||||
| 			for (Record5<Integer, String, String, String, EnumContacttype> r : sql.fetch()) { | ||||
| 				ContactBean bean = new ContactBean(r.get(T_CONTACT.PK)); | ||||
| 				bean.setForename(r.get(T_CONTACT.FORENAME)); | ||||
| 				bean.setSurname(r.get(T_CONTACT.SURNAME)); | ||||
| 				bean.setContact(r.get(T_CONTACT.CONTACT)); | ||||
| 				bean.setType(r.get(T_CONTACT.TYPE)); | ||||
| 				list.add(bean); | ||||
| 			} | ||||
| 			list.sort((o1, o2) -> o1 == null ? 0 : o1.compareTo(o2)); | ||||
| 			return list; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * delete a contact from the database | ||||
| 	 *  | ||||
| 	 * @param pk | ||||
| 	 *          the id of the contact | ||||
| 	 * @return the number of affected database rows, should be 1 | ||||
| 	 * @throws SQLException | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 * @throws DataAccessException | ||||
| 	 */ | ||||
| 	public Integer delete(Integer pk) throws DataAccessException, ClassNotFoundException, SQLException { | ||||
| 		try (CloseableDSLContext jooq = getJooq()) { | ||||
| 			DeleteConditionStep<TContactRecord> sql = jooq | ||||
| 			// @formatter:off | ||||
| 				.deleteFrom(T_CONTACT) | ||||
| 				.where(T_CONTACT.PK.eq(pk)); | ||||
| 			// @formatter:on | ||||
| 			LOGGER.debug("{}", sql.toString()); | ||||
| 			return sql.execute(); | ||||
| 		} | ||||
|  | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * add a contact to the database | ||||
| 	 * | ||||
| 	 * @param bean | ||||
| 	 *          the contact information | ||||
| 	 * @return the number of affected database rows, should be 1 | ||||
| 	 * @throws SQLException | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 * @throws DataAccessException | ||||
| 	 */ | ||||
| 	public Integer add(ContactBean bean) throws DataAccessException, ClassNotFoundException, SQLException { | ||||
| 		try (CloseableDSLContext jooq = getJooq()) { | ||||
| 			InsertValuesStep4<TContactRecord, String, String, String, EnumContacttype> sql = jooq | ||||
| 			// @formatter:off | ||||
| 				.insertInto(T_CONTACT, | ||||
| 						        T_CONTACT.FORENAME, | ||||
| 										T_CONTACT.SURNAME, | ||||
| 										T_CONTACT.CONTACT, | ||||
| 										T_CONTACT.TYPE) | ||||
| 				.values(bean.getForename(), bean.getSurname(), bean.getContact(), bean.getType()); | ||||
| 			// @formatter:on | ||||
| 			LOGGER.debug("{}", sql.toString()); | ||||
| 			return sql.execute(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * update a contact in the database, referencing its pk | ||||
| 	 *  | ||||
| 	 * @param bean | ||||
| 	 *          the contact information | ||||
| 	 * @return the number of affected database rows, should be 1 | ||||
| 	 * @throws SQLException | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 * @throws DataAccessException | ||||
| 	 */ | ||||
| 	public Integer update(ContactBean bean) throws DataAccessException, ClassNotFoundException, SQLException { | ||||
| 		try (CloseableDSLContext jooq = getJooq()) { | ||||
| 			UpdateConditionStep<TContactRecord> sql = jooq | ||||
| 			// @formatter:off | ||||
| 				.update(T_CONTACT) | ||||
| 				.set(T_CONTACT.FORENAME, bean.getForename()) | ||||
| 				.set(T_CONTACT.SURNAME, bean.getSurname()) | ||||
| 				.set(T_CONTACT.CONTACT, bean.getContact()) | ||||
| 				.set(T_CONTACT.TYPE, bean.getType()) | ||||
| 				.where(T_CONTACT.PK.eq(bean.getPk())); | ||||
| 			// @formatter:on | ||||
| 			LOGGER.debug("{}", sql.toString()); | ||||
| 			return sql.execute(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get number of entries in t_contact | ||||
| 	 *  | ||||
| 	 * @return number of entries | ||||
| 	 * @throws SQLException | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 * @throws DataAccessException | ||||
| 	 */ | ||||
| 	public Integer getAmount() throws DataAccessException, ClassNotFoundException, SQLException { | ||||
| 		try (CloseableDSLContext jooq = getJooq()) { | ||||
| 			SelectJoinStep<Record1<Integer>> sql = jooq | ||||
| 			// @formatter:off | ||||
| 				.selectCount() | ||||
| 				.from(T_CONTACT); | ||||
| 			// @formatter:on | ||||
| 			LOGGER.debug("{}", sql.toString()); | ||||
| 			return sql.fetchOne(DSL.count()); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get all enum types | ||||
| 	 *  | ||||
| 	 * @return list of enum types | ||||
| 	 */ | ||||
| 	public List<EnumContacttype> getTypes() { | ||||
| 		return new ArrayList<>(Arrays.asList(EnumContacttype.values())); | ||||
| 	} | ||||
| } | ||||
| @@ -1,111 +0,0 @@ | ||||
| package de.jottyfan.timetrack.modules.contact; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.sql.SQLException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.enterprise.context.SessionScoped; | ||||
| import javax.faces.application.FacesMessage; | ||||
| import javax.inject.Named; | ||||
|  | ||||
| import org.jooq.exception.DataAccessException; | ||||
|  | ||||
| import de.jooqfaces.JooqFacesContext; | ||||
| import de.jottyfan.timetrack.db.contact.enums.EnumContacttype; | ||||
| import de.jottyfan.timetrack.modules.Model; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  * | ||||
|  */ | ||||
| @Named | ||||
| @SessionScoped | ||||
| public class ContactModel implements Model, Serializable { | ||||
| 	private static final long serialVersionUID = 1L; | ||||
|  | ||||
| 	private ContactBean bean; | ||||
| 	private List<ContactBean> list; | ||||
| 	private List<EnumContacttype> types; | ||||
|  | ||||
| 	public boolean init(JooqFacesContext facesContext) { | ||||
| 		bean = new ContactBean(null); | ||||
| 		try { | ||||
| 			ContactGateway gw = new ContactGateway(facesContext); | ||||
| 			list = gw.getAll(); | ||||
| 			types = gw.getTypes(); | ||||
| 			return true; | ||||
| 		} catch (DataAccessException | ClassNotFoundException | SQLException e) { | ||||
| 			facesContext.addMessage(null, | ||||
| 					new FacesMessage(FacesMessage.SEVERITY_ERROR, "error on loading data from db", e.getMessage())); | ||||
| 			list = new ArrayList<>(); | ||||
| 			types = new ArrayList<>(); | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public boolean delete(JooqFacesContext facesContext) { | ||||
| 		try { | ||||
| 			Integer affected = new ContactGateway(facesContext).delete(bean.getPk()); | ||||
| 			return affected.equals(1); | ||||
| 		} catch (DataAccessException | ClassNotFoundException | SQLException e) { | ||||
| 			facesContext.addMessage(null, | ||||
| 					new FacesMessage(FacesMessage.SEVERITY_ERROR, "error on deleting data from db", e.getMessage())); | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public boolean add(JooqFacesContext facesContext) { | ||||
| 		try { | ||||
| 			Integer affected = new ContactGateway(facesContext).add(bean); | ||||
| 			return affected.equals(1); | ||||
| 		} catch (DataAccessException | ClassNotFoundException | SQLException e) { | ||||
| 			facesContext.addMessage(null, | ||||
| 					new FacesMessage(FacesMessage.SEVERITY_ERROR, "error on adding data to db", e.getMessage())); | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public boolean update(JooqFacesContext facesContext) { | ||||
| 		try { | ||||
| 			Integer affected = new ContactGateway(facesContext).update(bean); | ||||
| 			return affected.equals(1); | ||||
| 		} catch (DataAccessException | ClassNotFoundException | SQLException e) { | ||||
| 			facesContext.addMessage(null, | ||||
| 					new FacesMessage(FacesMessage.SEVERITY_ERROR, "error on updating data to db", e.getMessage())); | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public Integer getAmount(JooqFacesContext facesContext) { | ||||
| 		try { | ||||
| 			return new ContactGateway(facesContext).getAmount(); | ||||
| 		} catch (DataAccessException | ClassNotFoundException | SQLException e) { | ||||
| 			facesContext.addMessage(null, | ||||
| 					new FacesMessage(FacesMessage.SEVERITY_ERROR, "error on getting size of contacts", e.getMessage())); | ||||
| 			return -1; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public List<ContactBean> getList() { | ||||
| 		return list; | ||||
| 	} | ||||
|  | ||||
| 	public void setBean(ContactBean bean) { | ||||
| 		this.bean = bean; | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public ContactBean getBean() { | ||||
| 		return bean; | ||||
| 	} | ||||
|  | ||||
| 	public List<EnumContacttype> getTypes() { | ||||
| 		return types; | ||||
| 	} | ||||
|  | ||||
| 	public void setTypes(List<EnumContacttype> types) { | ||||
| 		this.types = types; | ||||
| 	} | ||||
| } | ||||
| @@ -1,16 +1,15 @@ | ||||
| package de.jottyfan.timetrack.modules.contact; | ||||
| package de.jottyfan.timetrack.spring.contact; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| import de.jottyfan.timetrack.db.contact.enums.EnumContacttype; | ||||
| import de.jottyfan.timetrack.modules.Bean; | ||||
| 
 | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  * | ||||
|  */ | ||||
| public class ContactBean implements Bean, Serializable, Comparable<ContactBean> { | ||||
| public class ContactBean implements Serializable, Comparable<ContactBean> { | ||||
| 	private static final long serialVersionUID = 1L; | ||||
| 
 | ||||
| 	private final Integer pk; | ||||
| @@ -0,0 +1,90 @@ | ||||
| package de.jottyfan.timetrack.spring.contact; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.annotation.security.RolesAllowed; | ||||
| import javax.servlet.ServletException; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.ModelAttribute; | ||||
| import org.springframework.web.bind.annotation.PathVariable; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMethod; | ||||
| import org.springframework.web.bind.annotation.ResponseBody; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| @Controller | ||||
| public class ContactController { | ||||
| 	private static final Logger LOGGER = LogManager.getLogger(ContactController.class); | ||||
|  | ||||
| 	private final HttpServletRequest request; | ||||
|  | ||||
| 	@Autowired | ||||
| 	private IContactService contactService; | ||||
|  | ||||
| 	@Autowired | ||||
| 	public ContactController(HttpServletRequest request) { | ||||
| 		this.request = request; | ||||
| 	} | ||||
|  | ||||
| 	@GetMapping("/logout") | ||||
| 	public String getLogout(HttpServletRequest request) throws ServletException { | ||||
| 		request.logout(); | ||||
| 		return "redirect:/"; | ||||
| 	} | ||||
|  | ||||
| 	@GetMapping("/") | ||||
| 	public String getIndex() { | ||||
| 		return "public/index"; | ||||
| 	} | ||||
| 	 | ||||
| 	@ModelAttribute("currentUser") | ||||
| 	@ResponseBody | ||||
| 	public String getCurrentUser() { | ||||
| 		return contactService.getCurrentUser(request); | ||||
| 	} | ||||
|  | ||||
| 	@RolesAllowed("timetrack_user") | ||||
| 	@RequestMapping(value = "/contact/contacts") | ||||
| 	public String getList(Model model, @ModelAttribute ContactBean bean) { | ||||
| 		List<ContactBean> list = contactService.toList(); | ||||
| 		model.addAttribute("contactList", list); | ||||
| 		return "contact/contacts"; | ||||
| 	} | ||||
|  | ||||
| 	@RolesAllowed("timetrack_user") | ||||
| 	@RequestMapping(value = "/contact/add/") | ||||
| 	public String toAdd(Model model) { | ||||
| 		return "contact/item"; | ||||
| 	} | ||||
|  | ||||
| 	@RolesAllowed("timetrack_user") | ||||
| 	@RequestMapping(value = "/contact/insert", method = RequestMethod.POST) | ||||
| 	public String doInsert(Model model, @ModelAttribute ContactBean bean) { | ||||
| 		Integer amount = contactService.doInsert(bean); | ||||
| 		return amount.equals(1) ? "contact/contacts" : "concact/item"; | ||||
| 	} | ||||
|  | ||||
| 	@RolesAllowed("timetrack_user") | ||||
|   @GetMapping("/contact/edit/{id}") | ||||
| 	public String toEdit(@PathVariable Integer id, Model model) { | ||||
| 		return "not yet implemented"; | ||||
| 	} | ||||
|  | ||||
| 	@RolesAllowed("timetrack_user") | ||||
| 	@RequestMapping(value = "/contact/delete/") | ||||
| 	public String doDelete(Model model, @ModelAttribute ContactBean bean) { | ||||
| 		Integer amount = contactService.doDelete(bean.getPk()); | ||||
| 		return amount.equals(1) ? "contact/contacts" : "contact/contacts"; | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,19 @@ | ||||
| package de.jottyfan.timetrack.spring.contact; | ||||
|  | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| public interface IContactService { | ||||
| 	public List<ContactBean> toList(); | ||||
| 	public Integer doInsert(ContactBean bean); | ||||
| 	public Integer doUdate(ContactBean bean); | ||||
| 	public Integer doDelete(Integer pk); | ||||
| 	public Integer getAmount(); | ||||
| 	public String getCurrentUser(HttpServletRequest request); | ||||
| } | ||||
| @@ -0,0 +1,169 @@ | ||||
| package de.jottyfan.timetrack.spring.contact.impl; | ||||
|  | ||||
| import static de.jottyfan.timetrack.db.contact.Tables.T_CONTACT; | ||||
|  | ||||
| import java.sql.SQLException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.jooq.DSLContext; | ||||
| import org.jooq.DeleteConditionStep; | ||||
| import org.jooq.InsertValuesStep4; | ||||
| import org.jooq.Record1; | ||||
| import org.jooq.Record5; | ||||
| import org.jooq.SelectJoinStep; | ||||
| import org.jooq.UpdateConditionStep; | ||||
| import org.jooq.exception.DataAccessException; | ||||
| import org.jooq.impl.DSL; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Repository; | ||||
|  | ||||
| import de.jottyfan.timetrack.db.contact.enums.EnumContacttype; | ||||
| import de.jottyfan.timetrack.db.contact.tables.records.TContactRecord; | ||||
| import de.jottyfan.timetrack.spring.contact.ContactBean; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author jotty | ||||
|  * | ||||
|  */ | ||||
| @Repository | ||||
| public class ContactGateway { | ||||
| 	private static final Logger LOGGER = LogManager.getLogger(ContactGateway.class); | ||||
| 	private final DSLContext jooq; | ||||
|  | ||||
| 	public ContactGateway(@Autowired DSLContext jooq) throws Exception { | ||||
| 		this.jooq = jooq; | ||||
| 	} | ||||
|  | ||||
| 	public DSLContext getJooq() { | ||||
| 		return this.jooq; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get sorted list of contacts | ||||
| 	 *  | ||||
| 	 * @return a list (an empty one at least) | ||||
| 	 * @throws SQLException | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 * @throws DataAccessException | ||||
| 	 */ | ||||
| 	public List<ContactBean> getAll() throws DataAccessException, ClassNotFoundException, SQLException { | ||||
| 		SelectJoinStep<Record5<Integer, String, String, String, EnumContacttype>> sql = getJooq() | ||||
| 		// @formatter:off | ||||
| 			.select(T_CONTACT.PK, | ||||
| 					    T_CONTACT.FORENAME, | ||||
| 					    T_CONTACT.SURNAME, | ||||
| 					    T_CONTACT.CONTACT, | ||||
| 					    T_CONTACT.TYPE) | ||||
| 			.from(T_CONTACT); | ||||
| 		// @formatter:on | ||||
| 		LOGGER.debug("{}", sql.toString()); | ||||
| 		List<ContactBean> list = new ArrayList<>(); | ||||
| 		for (Record5<Integer, String, String, String, EnumContacttype> r : sql.fetch()) { | ||||
| 			ContactBean bean = new ContactBean(r.get(T_CONTACT.PK)); | ||||
| 			bean.setForename(r.get(T_CONTACT.FORENAME)); | ||||
| 			bean.setSurname(r.get(T_CONTACT.SURNAME)); | ||||
| 			bean.setContact(r.get(T_CONTACT.CONTACT)); | ||||
| 			bean.setType(r.get(T_CONTACT.TYPE)); | ||||
| 			list.add(bean); | ||||
| 		} | ||||
| 		list.sort((o1, o2) -> o1 == null ? 0 : o1.compareTo(o2)); | ||||
| 		return list; | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * delete a contact from the database | ||||
| 	 *  | ||||
| 	 * @param pk the id of the contact | ||||
| 	 * @return the number of affected database rows, should be 1 | ||||
| 	 * @throws SQLException | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 * @throws DataAccessException | ||||
| 	 */ | ||||
| 	public Integer delete(Integer pk) throws DataAccessException, ClassNotFoundException, SQLException { | ||||
| 		DeleteConditionStep<TContactRecord> sql = getJooq() | ||||
| 		// @formatter:off | ||||
| 			.deleteFrom(T_CONTACT) | ||||
| 			.where(T_CONTACT.PK.eq(pk)); | ||||
| 		// @formatter:on | ||||
| 		LOGGER.debug("{}", sql.toString()); | ||||
| 		return sql.execute(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * add a contact to the database | ||||
| 	 * | ||||
| 	 * @param bean the contact information | ||||
| 	 * @return the number of affected database rows, should be 1 | ||||
| 	 * @throws SQLException | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 * @throws DataAccessException | ||||
| 	 */ | ||||
| 	public Integer add(ContactBean bean) throws DataAccessException, ClassNotFoundException, SQLException { | ||||
| 		InsertValuesStep4<TContactRecord, String, String, String, EnumContacttype> sql = getJooq() | ||||
| 		// @formatter:off | ||||
| 			.insertInto(T_CONTACT, | ||||
| 					        T_CONTACT.FORENAME, | ||||
| 									T_CONTACT.SURNAME, | ||||
| 									T_CONTACT.CONTACT, | ||||
| 									T_CONTACT.TYPE) | ||||
| 			.values(bean.getForename(), bean.getSurname(), bean.getContact(), bean.getType()); | ||||
| 		// @formatter:on | ||||
| 		LOGGER.debug("{}", sql.toString()); | ||||
| 		return sql.execute(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * update a contact in the database, referencing its pk | ||||
| 	 *  | ||||
| 	 * @param bean the contact information | ||||
| 	 * @return the number of affected database rows, should be 1 | ||||
| 	 * @throws SQLException | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 * @throws DataAccessException | ||||
| 	 */ | ||||
| 	public Integer update(ContactBean bean) throws DataAccessException, ClassNotFoundException, SQLException { | ||||
| 		UpdateConditionStep<TContactRecord> sql = getJooq() | ||||
| 		// @formatter:off | ||||
| 			.update(T_CONTACT) | ||||
| 			.set(T_CONTACT.FORENAME, bean.getForename()) | ||||
| 			.set(T_CONTACT.SURNAME, bean.getSurname()) | ||||
| 			.set(T_CONTACT.CONTACT, bean.getContact()) | ||||
| 			.set(T_CONTACT.TYPE, bean.getType()) | ||||
| 			.where(T_CONTACT.PK.eq(bean.getPk())); | ||||
| 		// @formatter:on | ||||
| 		LOGGER.debug("{}", sql.toString()); | ||||
| 		return sql.execute(); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get number of entries in t_contact | ||||
| 	 *  | ||||
| 	 * @return number of entries | ||||
| 	 * @throws SQLException | ||||
| 	 * @throws ClassNotFoundException | ||||
| 	 * @throws DataAccessException | ||||
| 	 */ | ||||
| 	public Integer getAmount() throws DataAccessException, ClassNotFoundException, SQLException { | ||||
| 		SelectJoinStep<Record1<Integer>> sql = getJooq() | ||||
| 		// @formatter:off | ||||
| 			.selectCount() | ||||
| 			.from(T_CONTACT); | ||||
| 		// @formatter:on | ||||
| 		LOGGER.debug("{}", sql.toString()); | ||||
| 		return sql.fetchOne(DSL.count()); | ||||
| 	} | ||||
|  | ||||
| 	/** | ||||
| 	 * get all enum types | ||||
| 	 *  | ||||
| 	 * @return list of enum types | ||||
| 	 */ | ||||
| 	public List<EnumContacttype> getTypes() { | ||||
| 		return new ArrayList<>(Arrays.asList(EnumContacttype.values())); | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,82 @@ | ||||
| package de.jottyfan.timetrack.spring.contact.impl; | ||||
|  | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.jooq.DSLContext; | ||||
| import org.keycloak.KeycloakSecurityContext; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
|  | ||||
| import de.jottyfan.timetrack.spring.contact.ContactBean; | ||||
| import de.jottyfan.timetrack.spring.contact.IContactService; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| @Service | ||||
| @Transactional(transactionManager = "transactionManager") | ||||
| public class ContactService implements IContactService { | ||||
| 	private static final Logger LOGGER = LogManager.getLogger(ContactService.class); | ||||
|  | ||||
| 	@Autowired | ||||
| 	private DSLContext dsl; | ||||
| 	 | ||||
| 	@Override | ||||
|   public String getCurrentUser(HttpServletRequest request) { | ||||
| 		KeycloakSecurityContext ksc = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName()); | ||||
| 		return ksc == null ? "" : ksc.getIdToken().getPreferredUsername(); | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public List<ContactBean> toList() { | ||||
| 		try { | ||||
| 			return new ContactGateway(dsl).getAll(); | ||||
| 		} catch (Exception e) { | ||||
| 			LOGGER.error(e); | ||||
| 			return new ArrayList<>(); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Integer doInsert(ContactBean bean) { | ||||
| 		try { | ||||
| 			return new ContactGateway(dsl).add(bean); | ||||
| 		} catch (Exception e) { | ||||
| 			LOGGER.error(e); | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Integer doUdate(ContactBean bean) { | ||||
| 		try { | ||||
| 			return new ContactGateway(dsl).update(bean); | ||||
| 		} catch (Exception e) { | ||||
| 			LOGGER.error(e); | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Integer doDelete(Integer pk) { | ||||
| 		try { | ||||
| 			return new ContactGateway(dsl).delete(pk); | ||||
| 		} catch (Exception e) { | ||||
| 			LOGGER.error(e); | ||||
| 			return 0; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	@Override | ||||
| 	public Integer getAmount() { | ||||
| 		return toList().size(); | ||||
| 	} | ||||
| } | ||||
| @@ -0,0 +1,181 @@ | ||||
| package de.jottyfan.timetrack.modules; | ||||
|  | ||||
| import java.math.BigDecimal; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| import javax.annotation.security.RolesAllowed; | ||||
| import javax.servlet.ServletException; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
|  | ||||
| import org.apache.logging.log4j.LogManager; | ||||
| import org.apache.logging.log4j.Logger; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.stereotype.Controller; | ||||
| import org.springframework.ui.Model; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.ModelAttribute; | ||||
| import org.springframework.web.bind.annotation.PathVariable; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMethod; | ||||
| import org.springframework.web.bind.annotation.ResponseBody; | ||||
|  | ||||
| /** | ||||
|  *  | ||||
|  * @author henkej | ||||
|  * | ||||
|  */ | ||||
| @Controller | ||||
| public class TimetrackController { | ||||
| 	private static final Logger LOGGER = LogManager.getLogger(TimetrackController.class); | ||||
| 	 | ||||
| 	private final HttpServletRequest request; | ||||
| 	 | ||||
| 	@Autowired | ||||
| 	private ITimetrackService fuelService; | ||||
|  | ||||
| 	private List<FuelBean> cachedFuels; | ||||
| 	 | ||||
| 	@Autowired | ||||
| 	public TimetrackController(HttpServletRequest request) { | ||||
| 		this.request = request; | ||||
| 	} | ||||
|  | ||||
| 	@GetMapping("/logout") | ||||
| 	public String getLogout(HttpServletRequest request) throws ServletException { | ||||
| 		request.logout(); | ||||
| 		return "redirect:/"; | ||||
| 	} | ||||
| 	 | ||||
| 	@RolesAllowed("car_user") | ||||
| 	@GetMapping("/secure/welcome") | ||||
| 	public String getWelcome() { | ||||
| 		return "secure/welcome"; | ||||
| 	} | ||||
| 	 | ||||
| 	@GetMapping("/") | ||||
| 	public String getIndex() { | ||||
| 		this.cachedFuels = null; | ||||
| 		return "public/index"; | ||||
| 	} | ||||
|  | ||||
| 	@RolesAllowed("car_user") | ||||
| 	@GetMapping("/secure/to_table") | ||||
| 	public String getTable() { | ||||
| 		this.cachedFuels = null; | ||||
| 		return "secure/table"; | ||||
| 	} | ||||
|  | ||||
| 	@RolesAllowed("car_user") | ||||
| 	@RequestMapping(value = "/secure/to_bean", method = RequestMethod.GET) | ||||
| 	public String getBean(Model model) { | ||||
| 		FuelBean bean = model.containsAttribute("fuelBean") ? (FuelBean) model.getAttribute("fuelBean") : new FuelBean(); | ||||
| 		model.addAttribute("fuelBean", bean); | ||||
| 		return "secure/bean"; | ||||
| 	} | ||||
| 	 | ||||
| 	@RolesAllowed("car_user") | ||||
| 	@RequestMapping(value = "/secure/to_existing_bean/{fkFuelBean}", method = RequestMethod.GET) | ||||
| 	public String getBean(Model model, @PathVariable Integer fkFuelBean) throws Exception { | ||||
| 		FuelBean bean = getFuelBean(fkFuelBean); | ||||
| 		model.addAttribute("fuelBean", bean); | ||||
| 		return "secure/bean"; | ||||
| 	} | ||||
|  | ||||
| 	private FuelBean getFuelBean(Integer fkFuelBean) throws Exception { | ||||
| 		for (FuelBean bean : getFuels()) { | ||||
| 			if (bean.getPk().equals(fkFuelBean)) { | ||||
| 				return bean; | ||||
| 			} | ||||
| 		} | ||||
| 		throw new Exception("bean not found"); | ||||
| 	} | ||||
|  | ||||
| 	@RolesAllowed("car_user") | ||||
| 	@RequestMapping(value = "/secure/do_upsert", method = RequestMethod.POST) | ||||
| 	public String getUpsert(Model model, @ModelAttribute FuelBean fuelBean) { | ||||
| 		Integer affected = fuelService.upsert(fuelBean); | ||||
| 		model.addAttribute("fuelBean", fuelBean); | ||||
| 		LOGGER.info("affected rows: {}", affected); | ||||
| 		return affected > 0 ? getTable() : getBean(model); | ||||
| 	} | ||||
|  | ||||
| 	@RolesAllowed("car_user") | ||||
| 	@GetMapping("/secure/jsonfuels") | ||||
| 	@ResponseBody | ||||
| 	public List<FuelBean> getJsonFuels() { | ||||
| 		List<FuelBean> fuels = fuelService.findAll(); | ||||
| 		return fuels; | ||||
| 	} | ||||
|  | ||||
| 	@ModelAttribute("fuels") | ||||
| 	public List<FuelBean> getFuels() { | ||||
| 		if (cachedFuels == null || cachedFuels.size() < 1) { | ||||
| 			cachedFuels = fuelService.findAll(); | ||||
| 		} | ||||
| 		return cachedFuels; | ||||
| 	} | ||||
| 	 | ||||
| 	@ModelAttribute("currentUser") | ||||
| 	@ResponseBody | ||||
| 	public String getCurrentUser() { | ||||
| 		return fuelService.getCurrentUser(request); | ||||
| 	} | ||||
|  | ||||
| 	@ModelAttribute("averagelkm") | ||||
| 	@ResponseBody | ||||
| 	public BigDecimal getAverageLiterPerKm() { | ||||
| 		List<FuelBean> fuels = getFuels(); | ||||
| 		Integer minMileage = 1000000; // my car won't even reach this milestone :) | ||||
| 		Integer maxMileage = 0; | ||||
| 		BigDecimal summedAmount = new BigDecimal(0); | ||||
| 		for (FuelBean bean : fuels) { | ||||
| 			summedAmount = summedAmount.add(bean.getAmount()); | ||||
| 			minMileage = bean.getMileage() < minMileage ? bean.getMileage() : minMileage; | ||||
| 			maxMileage = bean.getMileage() > maxMileage ? bean.getMileage() : maxMileage; | ||||
| 		} | ||||
| 		BigDecimal totalMileage = new BigDecimal(maxMileage - minMileage); | ||||
| 		BigDecimal calculated = totalMileage.intValue() != 0 | ||||
| 				? new BigDecimal((summedAmount.doubleValue() / totalMileage.doubleValue()) * 100d) | ||||
| 				: new BigDecimal(0); | ||||
| 		return calculated; | ||||
| 	} | ||||
|  | ||||
| 	@ModelAttribute("averageel") | ||||
| 	@ResponseBody | ||||
| 	public BigDecimal getAverageEuroPerLiter() { | ||||
| 		List<FuelBean> fuels = getFuels(); | ||||
| 		BigDecimal summedLiter = new BigDecimal(0); | ||||
| 		BigDecimal summedEuro = new BigDecimal(0); | ||||
| 		for (FuelBean bean : fuels) { | ||||
| 			summedLiter = summedLiter.add(bean.getAmount()); | ||||
| 			summedEuro = summedEuro.add(bean.getPrice()); | ||||
| 		} | ||||
| 		BigDecimal calculated = summedLiter.intValue() != 0 | ||||
| 				? new BigDecimal(summedEuro.doubleValue() / summedLiter.doubleValue()) | ||||
| 				: new BigDecimal(0); | ||||
| 		return calculated; | ||||
| 	} | ||||
|  | ||||
| 	@ModelAttribute("chartjsdata") | ||||
| 	@ResponseBody | ||||
| 	public List<BigDecimal> getChartjsData() { | ||||
| 		List<FuelBean> fuels = getFuels(); | ||||
| 		List<BigDecimal> list = new ArrayList<>(); | ||||
| 		for (FuelBean bean : fuels) { | ||||
| 			list.add(bean.getEuro_per_l()); | ||||
| 		} | ||||
| 		return list; | ||||
| 	} | ||||
|  | ||||
| 	@ModelAttribute("chartjslabel") | ||||
| 	@ResponseBody | ||||
| 	public List<Integer> getChartjsLabel() { | ||||
| 		List<FuelBean> fuels = getFuels(); | ||||
| 		List<Integer> list = new ArrayList<>(); | ||||
| 		for (FuelBean bean : fuels) { | ||||
| 			list.add(bean.getMileage()); | ||||
| 		} | ||||
| 		return list; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										21
									
								
								src/main/resources/application.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/main/resources/application.properties
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| # jooq | ||||
| spring.datasource.driver-class-name=org.postgresql.Driver | ||||
| spring.datasource.url=jdbc:postgresql://localhost:5432/timetrack | ||||
| spring.datasource.username=timetrack | ||||
| spring.datasource.password=timetrack | ||||
|  | ||||
| # application | ||||
| server.port = 8083 | ||||
|  | ||||
| server.servlet.context-path=/timetrack | ||||
|  | ||||
| # keycloak | ||||
| keycloak.auth-server-url = http://localhost:8080/ | ||||
| keycloak.realm = jottyfan | ||||
| keycloak.resource = timetrack | ||||
| keycloak.public-client = true | ||||
| keycloak.security-constraints[0].authRoles[0] = timetrack_user | ||||
| keycloak.security-constraints[0].securityCollections[0].patterns[0] = /* | ||||
| #keycloak.credentia | ||||
| keycloak.use-resource-role-mappings=true | ||||
| #keycloak.bearer-only=true | ||||
| @@ -1,3 +1,12 @@ | ||||
| html { | ||||
| 	height: 100%; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
| 	background-color: #eee; | ||||
| 	height: calc(100% - 76px); | ||||
| } | ||||
| 
 | ||||
| .body { | ||||
| 	height: 100%; | ||||
| 	width: 100%; | ||||
| @@ -7,7 +16,8 @@ | ||||
| .page { | ||||
| 	height: 100%; | ||||
| 	width: 100%; | ||||
| 	background-image: linear-gradient(to bottom, #ffffff 10%, #afffff 40%) | ||||
| 	padding-bottom: 12px; | ||||
| 	background-image: linear-gradient(to bottom, #eee, #777) | ||||
| 		!important; | ||||
| } | ||||
| 
 | ||||
							
								
								
									
										31
									
								
								src/main/resources/templates/contact/contacts.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/main/resources/templates/contact/contacts.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| <!DOCTYPE html> | ||||
| <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" | ||||
| 	xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}"> | ||||
| <head> | ||||
| <title>Kontakte</title> | ||||
| </head> | ||||
| <body> | ||||
| 	<ul layout:fragment="menu"> | ||||
| 		<li class="nav-item" sec:authorize="hasRole('timetrack_user')"><a class="nav-link" th:href="@{/contact/add}">Neuen | ||||
| 				Kontakt anlegen</a></li> | ||||
| 	</ul> | ||||
| 	<main layout:fragment="content"> | ||||
| 		<div class="row row-cols-12 ro-cols-lg-4 ro-cols-md-3 ro-cols-sd-2 g-4" style="margin: 8px"> | ||||
| 			<div class="col" th:each="contact : ${contactList}"> | ||||
| 				<div class="card text-dark bg-light shadow" style="width: 18rem"> | ||||
| 					<div class="card-header text-center"> | ||||
| 						<font th:text="${contact.forename} + ' ' + ${contact.surname}" style="font-size: larger"></font> | ||||
| 					</div> | ||||
| 					<div class="card-body"> | ||||
| 						<div class="d-flex justify-content-center align-items-center"> | ||||
| 							<span th:text="${contact.type} + ': ' + ${contact.contact}"></span> <a th:href="@{/contact/edit/{id}(id=${contact.pk})}" | ||||
| 								sec:authorize="hasRole('timetrack_user')" style="margin-left: 8px;"> <i class="fa fa-edit"></i> | ||||
| 							</a> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</main> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										34
									
								
								src/main/resources/templates/contact/item.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/main/resources/templates/contact/item.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| <!DOCTYPE html> | ||||
| <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" | ||||
| 	xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}"> | ||||
| <head> | ||||
| <title>Kontakt aktualisieren</title> | ||||
| </head> | ||||
| <body> | ||||
| 	<ul layout:fragment="menu"> | ||||
| 		<li class="nav-item" sec:authorize="hasRole('timetrack_user')"><a class="nav-link" th:href="@{/contact/contacts}">abbrechen</a></li> | ||||
| 	</ul> | ||||
| 	<main layout:fragment="content"> | ||||
| 		<div class="container"> | ||||
| 			<form th:action="@{/contact/insert}" th:object="${contactBean}" method="post"> | ||||
| 				<p> | ||||
| 					Inhalt von Eintrag <span th:text="*{pk}"></span>: | ||||
| 				</p> | ||||
| 				<input type="hidden" th:field="*{pk}" /> | ||||
| 				<div class="form-group"> | ||||
| 					<label>Vorname</label> <input type="text" th:field="*{forename}" class="form-control" /> | ||||
| 				</div> | ||||
| 				<div class="form-group"> | ||||
| 					<label>Nachname</label> <input type="text" th:field="*{surname}" class="form-control" /> | ||||
| 				</div> | ||||
| 				<div class="form-group"> | ||||
| 					<label>Kontakt</label> <input type="text" th:field="*{contact}" class="form-control" /> | ||||
| 				</div> | ||||
| 				<div class="form-group"> | ||||
| 					<label>Typ</label> <input type="text" th:field="*{type}" class="form-control" /> | ||||
| 				</div> | ||||
| 			</form> | ||||
| 		</div> | ||||
| 	</main> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										20
									
								
								src/main/resources/templates/error.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/main/resources/templates/error.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| <!DOCTYPE html> | ||||
| <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" | ||||
| 	xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}"> | ||||
| <head> | ||||
| <title>Timetrack</title> | ||||
| </head> | ||||
| <body> | ||||
| 	<ul layout:fragment="menu"> | ||||
| 	</ul> | ||||
| 	<main layout:fragment="content"> | ||||
| 		<div style="margin: 10vh"> | ||||
| 			<div class="alert alert-danger" role="alert" style="vertical-align: middle; align: center"> | ||||
| 				<p>Es ist ein Fehler aufgetreten.</p> | ||||
| 				<a th:href="@{/}" class="alert-link">Ach, Mist...</a> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</main> | ||||
| </body> | ||||
| </html> | ||||
|  | ||||
							
								
								
									
										46
									
								
								src/main/resources/templates/layout/main.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/main/resources/templates/layout/main.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| <!DOCTYPE html> | ||||
| <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> | ||||
| <head> | ||||
| <meta charset="UTF-8" /> | ||||
|  | ||||
| <link rel="stylesheet" type="text/css" th:href="@{/webjars/bootstrap/5.1.3/css/bootstrap.min.css}" /> | ||||
| <link rel="stylesheet" type="text/css" th:href="@{/webjars/datatables/1.11.3/css/dataTables.bootstrap5.min.css}" /> | ||||
| <link rel="stylesheet" type="text/css" th:href="@{/webjars/font-awesome/5.15.4/css/all.min.css}"> | ||||
| <link rel="stylesheet" type="text/css" th:href="@{/css/style.css}"> | ||||
| <link rel="icon" type="image/png" sizes="32x32" th:href="@{/png/favicon/favicon-32x32.png}"> | ||||
| <link rel="icon" type="image/png" sizes="16x16" th:href="@{/png/favicon/favicon-16x16.png}"> | ||||
|  | ||||
| <script th:src="@{/webjars/jquery/3.6.0/jquery.min.js}"></script> | ||||
| <script th:src="@{/webjars/bootstrap/5.1.3/js/bootstrap.bundle.min.js}"></script> | ||||
| <script th:src="@{/webjars/datatables/1.11.3/js/jquery.dataTables.min.js}"></script> | ||||
| <script th:src="@{/webjars/datatables/1.11.3/js/dataTables.bootstrap5.min.js}"></script> | ||||
|  | ||||
| </head> | ||||
| <body> | ||||
| 	<!-- Navigation --> | ||||
| 	<nav class="navbar navbar-expand-lg navbar-light bg-light static-top"> | ||||
| 		<div class="container-fluid" style="width: 98%"> | ||||
| 			<a class="navbar-brand" th:href="@{/}"> <i class="fa fa-calendar-alt"></i> Timetrack | ||||
| 				<div class="version" th:text="${@manifestBean.getVersion()}"></div> | ||||
| 			</a> | ||||
| 			<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" | ||||
| 				aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> | ||||
| 				<span class="navbar-toggler-icon"></span> | ||||
| 			</button> | ||||
| 			<div class="collapse navbar-collapse" id="navbarResponsive"> | ||||
| 				<ul class="navbar-nav ml-auto"> | ||||
| 					<li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" id="navbarScrollingDropdown" role="button" | ||||
| 						data-bs-toggle="dropdown" aria-expanded="false"> Module </a> | ||||
| 						<ul class="dropdown-menu dropdown-menu-light" aria-labelledby="navbarScrollingDropdown"> | ||||
| 							<li><a class="dropdown-item" th:href="@{/contact/contacts}">Kontakte</a></li> | ||||
| 							<li><hr /></li> | ||||
| 							<li><a class="dropdown-item" th:href="@{/logout}">[[${currentUser}]] abmelden</a></li> | ||||
| 						</ul></li> | ||||
| 					<li layout:fragment="menu" style="list-style-type: none"></li> | ||||
| 				</ul> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</nav> | ||||
| 	<main layout:fragment="content" class="page body"></main> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										12
									
								
								src/main/resources/templates/public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/main/resources/templates/public/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <!DOCTYPE html> | ||||
| <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" | ||||
| 	xmlns:sec="http://www.thymeleaf.org/extras/spring-security" layout:decorate="~{layout/main.html}"> | ||||
| <head> | ||||
| <title>Timetrack</title> | ||||
| </head> | ||||
| <body> | ||||
| 	<ul layout:fragment="menu"> | ||||
| 	</ul> | ||||
| 	<main layout:fragment="content"></main> | ||||
| </body> | ||||
| </html> | ||||
		Reference in New Issue
	
	Block a user